packages feed

wireform-core-0.2.0.0: test/Wireform/Base64/Test.hs

{-# LANGUAGE OverloadedStrings #-}

module Wireform.Base64.Test (spec) where

import Data.ByteString qualified as BS
import Data.Word (Word8)
import Test.QuickCheck
import Test.Syd
import Wireform.Base64


spec :: Spec
spec = describe "Wireform.Base64" $ do
  describe "RFC 4648 sec 10 test vectors" $ do
    it "encodes the canonical vectors" $ do
      encodeBase64 "" `shouldBe` ""
      encodeBase64 "f" `shouldBe` "Zg=="
      encodeBase64 "fo" `shouldBe` "Zm8="
      encodeBase64 "foo" `shouldBe` "Zm9v"
      encodeBase64 "foob" `shouldBe` "Zm9vYg=="
      encodeBase64 "fooba" `shouldBe` "Zm9vYmE="
      encodeBase64 "foobar" `shouldBe` "Zm9vYmFy"

    it "decodes the canonical vectors" $ do
      decodeBase64 "" `shouldBe` Just ""
      decodeBase64 "Zg==" `shouldBe` Just "f"
      decodeBase64 "Zm8=" `shouldBe` Just "fo"
      decodeBase64 "Zm9v" `shouldBe` Just "foo"
      decodeBase64 "Zm9vYg==" `shouldBe` Just "foob"
      decodeBase64 "Zm9vYmE=" `shouldBe` Just "fooba"
      decodeBase64 "Zm9vYmFy" `shouldBe` Just "foobar"

  describe "round-trip" $ do
    it "decodes its own encoding for inputs up to 256 bytes" $
      property $ \(bytes :: [Word8Wrap]) ->
        let bs = BS.pack (map unwrap (take 256 bytes))
        in decodeBase64 (encodeBase64 bs) === Just bs

    it "drives the SIMD body (>= 16 chars)" $ do
      let bs = BS.pack [0 .. 47]
      decodeBase64 (encodeBase64 bs) `shouldBe` Just bs

    it "drives the SIMD body and tail (4096 bytes)" $ do
      let bs = BS.pack (take 4096 (cycle [0 .. 255]))
      decodeBase64 (encodeBase64 bs) `shouldBe` Just bs

  describe "rejection" $ do
    it "rejects non-multiple-of-4 inputs" $
      decodeBase64 "Zm9v=" `shouldBe` Nothing

    it "rejects out-of-alphabet bytes" $
      decodeBase64 "Zm9*" `shouldBe` Nothing

    it "rejects bad padding position" $
      decodeBase64 "Z===" `shouldBe` Nothing

  describe "length helpers" $ do
    it "encodeBase64Length matches the actual encoded length" $
      property $ \n ->
        let n' = abs n `mod` 1024
            bs = BS.replicate n' 0
        in BS.length (encodeBase64 bs) === encodeBase64Length n'


-- Small wrapper to bound Arbitrary Word8 in the property test
-- without dragging in QuickCheck-instances.
newtype Word8Wrap = Word8Wrap {unwrap :: Word8}
  deriving stock (Show)


instance Arbitrary Word8Wrap where
  arbitrary = Word8Wrap . fromIntegral <$> chooseInt (0, 255)