packages feed

age-0.0.1.0: test/Test/Crypto/Age/Scrypt.hs

{-# LANGUAGE TemplateHaskell #-}

module Test.Crypto.Age.Scrypt
  ( tests
  , goldenWorkFactor
  ) where

import Crypto.Age.Scrypt
  ( WorkFactor
  , bytesToSalt
  , mkWorkFactor
  , saltToBytes
  , workFactorBuilder
  , workFactorParser
  )
import Data.Attoparsec.ByteString ( parseOnly )
import Data.ByteString ( ByteString )
import qualified Data.ByteString as BS
import Data.ByteString.Builder ( Builder, toLazyByteString )
import qualified Data.ByteString.Builder as Builder
import Data.Word ( Word8 )
import Hedgehog
  ( Property, checkParallel, discover, forAll, property, tripping )
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
import Prelude
import Test.Crypto.Age.Scrypt.Gen ( genSalt, genWorkFactorInRange )
import Test.Golden ( goldenTestWithEncoderAndDecoder )

tests :: IO Bool
tests = checkParallel $$(discover)

------------------------------------------------------------------------------
-- Properties
------------------------------------------------------------------------------

-- | Test that 'bytesToSalt' only returns 'Just' when given 16 bytes.
prop_bytesToSalt :: Property
prop_bytesToSalt = property $ do
  bs <- forAll $ Gen.bytes (Range.constant 0 256)
  let bsLen = BS.length bs
  case bytesToSalt bs of
    Nothing
      | bsLen == 16 -> fail "failed when given 16 bytes"
      | otherwise -> pure ()
    Just _
      | bsLen == 16 -> pure ()
      | otherwise -> fail $ "succeeded when given " <> show bsLen <> " bytes"

-- | Test that 'saltToBytes' and 'bytesToSalt' round trip.
prop_roundTrip_saltBytes :: Property
prop_roundTrip_saltBytes = property $ do
  salt <- forAll genSalt
  tripping
    salt
    saltToBytes
    bytesToSalt

-- | Test that 'mkWorkFactor' only returns 'Just' when given an integer
-- @0 < n <= 64@.
prop_mkWorkFactor :: Property
prop_mkWorkFactor = property $ do
  n <- forAll $ Gen.word8 Range.constantBounded
  case mkWorkFactor n of
    Nothing
      | isValid n -> fail $ "failed when given " <> show n
      | otherwise -> pure ()
    Just _
      | isValid n -> pure ()
      | otherwise -> fail $ "succeeded when given " <> show n
  where
    isValid :: Word8 -> Bool
    isValid w8
      | w8 >= 1 && w8 <= 64 = True
      | otherwise = False

-- | Test that 'workFactorBuilder' and 'workFactorParser' round trip.
prop_roundTrip_encodeParseWorkFactor :: Property
prop_roundTrip_encodeParseWorkFactor = property $ do
  workFactor <- forAll $ genWorkFactorInRange (Range.constant 1 64)
  tripping
    workFactor
    (toStrictByteString . workFactorBuilder)
    (parseOnly workFactorParser)

-- | Test that 'workFactorParser' fails when parsing a work factor with leading
-- zeroes.
prop_workFactorParser_failOnLeadingZeroes :: Property
prop_workFactorParser_failOnLeadingZeroes = property $ do
  workFactor <- forAll $ genWorkFactorInRange (Range.constant 1 64)
  let encodedWorkFactor = toStrictByteString (workFactorBuilder workFactor)
  leadingZeroes <- forAll $ Gen.utf8 (Range.constant 1 256) (pure '0')
  let encodedWorkFactorWithLeadingZeroes = leadingZeroes <> encodedWorkFactor
  case parseOnly workFactorParser encodedWorkFactorWithLeadingZeroes of
    Left _ -> pure ()
    Right _ -> fail $ "succeeded when parsing " <> show encodedWorkFactorWithLeadingZeroes

-- | Test that 'workFactorParser' fails when parsing a work factor that is @0@
-- or greater than @64@.
prop_workFactorParser_cannotParseIntOutOfRange :: Property
prop_workFactorParser_cannotParseIntOutOfRange = property $ do
  n <- forAll $ Gen.word Range.linearBounded
  let encoded = toStrictByteString (Builder.wordDec n)
  case parseOnly workFactorParser encoded of
    Left _
      | n <= 0 || n > 64 -> pure ()
      | otherwise -> fail $ "failed when parsing " <> show n
    Right _
      | n > 0 && n <= 64 -> pure ()
      | otherwise -> fail $ "succeeded when parsing " <> show n

prop_golden_workFactor :: Property
prop_golden_workFactor =
  goldenTestWithEncoderAndDecoder
    (toStrictByteString . workFactorBuilder)
    (parseOnly workFactorParser)
    goldenWorkFactor
    "test/golden/binary/work-factor/golden.bin"

------------------------------------------------------------------------------
-- Golden examples
------------------------------------------------------------------------------

goldenWorkFactor :: WorkFactor
goldenWorkFactor =
  case mkWorkFactor 13 of
    Nothing -> error "goldenWorkFactor: impossible: could not construct work factor"
    Just x -> x

------------------------------------------------------------------------------
-- Helpers
------------------------------------------------------------------------------

toStrictByteString :: Builder -> ByteString
toStrictByteString = BS.toStrict . toLazyByteString