packages feed

fernet-0.1.0.0: test/Network/Fernet/Tests.hs

{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE DeriveGeneric #-}

module Network.Fernet.Tests (makeTests) where

import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Char8 as S8
import Data.Maybe (fromJust, fromMaybe)
import Data.Either (isLeft)
import Data.Aeson
import Control.Applicative
import Data.Time.Clock (NominalDiffTime)
import Data.Time.LocalTime (zonedTimeToUTC)
import Data.Time.Format (parseTimeM, defaultTimeLocale)
import Data.Time.Clock.POSIX (POSIXTime, utcTimeToPOSIXSeconds)
import GHC.Generics
import System.IO

import Test.QuickCheck
import Test.QuickCheck.Monadic
import qualified Test.QuickCheck         as QC
import qualified Test.QuickCheck.Monadic as QC
import           Test.Tasty              (TestTree, testGroup)
import           Test.Tasty.HUnit        (testCase)
import           Test.HUnit              (Assertion, (@?=), assertFailure)

import Network.Fernet

data Spec = Spec
            { desc :: String
            , token :: ByteString
            , now :: POSIXTime
            , ttl :: NominalDiffTime
            , secret :: ByteString
            , src :: ByteString
            , iv :: ByteString
            } deriving (Generic, Show)

instance FromJSON Spec where
    parseJSON = withObject "Spec" $ \v -> Spec
        <$> v .:? "desc" .!= ""
        <*> liftA S8.pack (v .: "token")
        <*> liftA (fromMaybe 0 . parseTime) (v .: "now")
        <*> liftA (fromIntegral :: Int -> NominalDiffTime) (v .:? "ttl_sec" .!= 0)
        <*> liftA S8.pack (v .: "secret")
        <*> liftA S8.pack (v .:? "src" .!= "")
        <*> liftA BS.pack (v .:? "iv" .!= [])

parseTime :: Monad m => String -> m POSIXTime
parseTime = fmap (utcTimeToPOSIXSeconds . zonedTimeToUTC) . parseTimeM False defaultTimeLocale fmt
  where fmt = "%Y-%m-%dT%H:%M:%S%z"

makeTests :: IO TestTree
makeTests = do
  generate <- makeGroup "generate" generateTest
  verify <- makeGroup "verify" verifyTest
  invalid <- makeGroup "invalid" invalidTest
  return $ testGroup "Acceptance Tests" [ generate, verify, invalid ]

makeGroup :: String -> (Spec -> TestTree) -> IO TestTree
makeGroup name makeSpec = do
  let f = "spec/" ++ name ++ ".json"
  withFile f ReadMode $ \h -> do
    c <- BL.hGetContents h
    case eitherDecode' c of
      Right specs -> return $ testGroup name (map makeSpec specs)
      Left e -> return $ testGroup name [testCase ("Loading " ++ f) (assertFailure e)]

getKey :: ByteString -> IO Key
getKey secret = case keyFromBase64 secret of
                  Right key -> return key
                  Left e -> fail $ "Couldn't decode secret: " ++ e

generateTest :: Spec -> TestTree
generateTest Spec{..} = testCase "Token generation" $ do
  key <- getKey secret
  encrypt' key now iv src @?= token

verifyTest :: Spec -> TestTree
verifyTest Spec{..} = testCase "Successful token verification" $ do
  key <- getKey secret
  decrypt' key ttl now token @?= Right src

invalidTest :: Spec -> TestTree
invalidTest Spec{..} = testCase desc $ do
  key <- getKey secret
  let r = decrypt' key ttl now token
  isLeft r @?= True