packages feed

sd-jwt-0.1.0.0: test/SerializationSpec.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
module SerializationSpec (spec) where

import Test.Hspec
import Test.QuickCheck
import Test.QuickCheck.Property ((==>))
import TestHelpers
import TestKeys
import SDJWT.Internal.Types
import SDJWT.Internal.Utils
import SDJWT.Internal.Digest
import SDJWT.Internal.Disclosure
import SDJWT.Internal.Serialization
import SDJWT.Internal.Issuance
import SDJWT.Internal.Presentation
import SDJWT.Internal.Verification (verifySDJWT, verifySDJWTSignature, verifySDJWTWithoutSignature, verifyKeyBinding, verifyDisclosures, extractHashAlgorithm)
import SDJWT.Internal.KeyBinding
import SDJWT.Internal.JWT
import qualified Data.Vector as V
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Key as Key
import qualified Data.Aeson.KeyMap as KeyMap
import qualified Data.Text as T
import Data.Text.Encoding (encodeUtf8, decodeUtf8')
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BSL
import qualified Data.Map.Strict as Map
import Data.Int (Int64)
import Data.Maybe (isJust, mapMaybe)
import Data.List (find, nub)
import Control.Monad (replicateM)

spec :: Spec
spec =     describe "SDJWT.Serialization" $ do
    describe "serializeSDJWT" $ do
      it "serializes SD-JWT with empty disclosures" $ do
        let jwt = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.test"
        let sdjwt = SDJWT jwt []
        serializeSDJWT sdjwt `shouldBe` "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.test~"
      
      it "serializes SD-JWT with single disclosure" $ do
        let jwt = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.test"
        let disclosure = EncodedDisclosure "disclosure1"
        let sdjwt = SDJWT jwt [disclosure]
        serializeSDJWT sdjwt `shouldBe` "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.test~disclosure1~"
      
      it "serializes SD-JWT with multiple disclosures" $ do
        let jwt = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.test"
        let disclosure1 = EncodedDisclosure "disclosure1"
        let disclosure2 = EncodedDisclosure "disclosure2"
        let disclosure3 = EncodedDisclosure "disclosure3"
        let sdjwt = SDJWT jwt [disclosure1, disclosure2, disclosure3]
        serializeSDJWT sdjwt `shouldBe` "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.test~disclosure1~disclosure2~disclosure3~"
    
    describe "parseTildeSeparated" $ do
      it "parses SD-JWT format" $ do
        let input = "jwt~disclosure1~disclosure2~"
        case parseTildeSeparated input of
          Right (jwt, parsedDisclosures, Nothing) -> do
            jwt `shouldBe` "jwt"
            length parsedDisclosures `shouldBe` 2
          Right (_, _, Just _) -> expectationFailure "Unexpected key binding JWT"
          Left err -> expectationFailure $ "Failed to parse: " ++ show err
      
      it "parses SD-JWT+KB format" $ do
        let input = "jwt~disclosure1~disclosure2~kb-jwt"
        case parseTildeSeparated input of
          Right (jwt, parsedDisclosures, Just kbJwt) -> do
            jwt `shouldBe` "jwt"
            length parsedDisclosures `shouldBe` 2
            kbJwt `shouldBe` "kb-jwt"
          Right _ -> expectationFailure "Expected KB-JWT"
          Left err -> expectationFailure $ "Failed to parse: " ++ show err
      
      it "parses JWT only (no disclosures)" $ do
        let input = "jwt"
        case parseTildeSeparated input of
          Right (jwt, parsedDisclosures, Nothing) -> do
            jwt `shouldBe` "jwt"
            length parsedDisclosures `shouldBe` 0
          Left err -> expectationFailure $ "Failed to parse: " ++ show err
          _ -> expectationFailure "Unexpected result"
      
      it "parses empty string as JWT-only" $ do
        let input = ""
        case parseTildeSeparated input of
          Right (jwt, parsedDisclosures, mbKbJwt) -> do
            jwt `shouldBe` ""
            length parsedDisclosures `shouldBe` 0
            mbKbJwt `shouldBe` Nothing
          Left err -> expectationFailure $ "Should parse empty string, got error: " ++ show err
      
      it "handles multiple consecutive tildes" $ do
        let input = "jwt~~disclosure1~~"
        case parseTildeSeparated input of
          Right (jwt, parsedDisclosures, Nothing) -> do
            jwt `shouldBe` "jwt"
            length parsedDisclosures `shouldBe` 3  -- empty, disclosure1, empty
          Left err -> expectationFailure $ "Failed to parse: " ++ show err
          _ -> expectationFailure "Unexpected result"
    
    describe "deserializeSDJWT" $ do
      it "deserializes valid SD-JWT" $ do
        let jwt = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.test"
        let input = jwt <> "~disclosure1~disclosure2~"
        case deserializeSDJWT input of
          Right (SDJWT parsedJwt parsedDisclosures) -> do
            parsedJwt `shouldBe` jwt
            length parsedDisclosures `shouldBe` 2
            unEncodedDisclosure (head parsedDisclosures) `shouldBe` "disclosure1"
            unEncodedDisclosure (parsedDisclosures !! 1) `shouldBe` "disclosure2"
          Left err -> expectationFailure $ "Failed to deserialize: " ++ show err
      
      it "deserializes SD-JWT with no disclosures" $ do
        let jwt = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.test"
        let input = jwt <> "~"
        case deserializeSDJWT input of
          Right (SDJWT parsedJwt parsedDisclosures) -> do
            parsedJwt `shouldBe` jwt
            length parsedDisclosures `shouldBe` 0
          Left err -> expectationFailure $ "Failed to deserialize: " ++ show err
      
      it "rejects SD-JWT+KB format" $ do
        let jwt = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.test"
        let input = jwt <> "~disclosure1~kb-jwt"
        case deserializeSDJWT input of
          Left _ -> return ()  -- Expected error
          Right _ -> expectationFailure "Should reject SD-JWT+KB format"
      
      it "handles empty input (parses as empty JWT)" $ do
        case deserializeSDJWT "" of
          Right (SDJWT parsedJwt parsedDisclosures) -> do
            parsedJwt `shouldBe` ""
            length parsedDisclosures `shouldBe` 0
          Left _ -> return ()  -- Empty JWT might be rejected by deserializeSDJWT validation
      
      it "rejects input without trailing tilde (parses as SD-JWT+KB)" $ do
        let jwt = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.test"
        let input = jwt <> "~disclosure1"  -- Missing trailing tilde - parses as SD-JWT+KB
        case deserializeSDJWT input of
          Left _ -> return ()  -- Expected error (SD-JWT format requires trailing tilde, this parses as SD-JWT+KB)
          Right _ -> expectationFailure "Should reject SD-JWT+KB format"
    
    describe "deserializePresentation" $ do
      it "deserializes SD-JWT presentation (no KB-JWT)" $ do
        let jwt = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.test"
        let input = jwt <> "~disclosure1~disclosure2~"
        case deserializePresentation input of
          Right (SDJWTPresentation parsedJwt parsedDisclosures Nothing) -> do
            parsedJwt `shouldBe` jwt
            length parsedDisclosures `shouldBe` 2
          Left err -> expectationFailure $ "Failed to deserialize: " ++ show err
          Right _ -> expectationFailure "Unexpected KB-JWT"
      
      it "deserializes SD-JWT+KB presentation" $ do
        let jwt = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.test"
        let kbJwt = "kb-jwt-token"
        let input = jwt <> "~disclosure1~disclosure2~" <> kbJwt
        case deserializePresentation input of
          Right (SDJWTPresentation parsedJwt parsedDisclosures (Just parsedKbJwt)) -> do
            parsedJwt `shouldBe` jwt
            length parsedDisclosures `shouldBe` 2
            parsedKbJwt `shouldBe` kbJwt
          Left err -> expectationFailure $ "Failed to deserialize: " ++ show err
          Right _ -> expectationFailure "Expected KB-JWT"
      
      it "deserializes presentation with no disclosures" $ do
        let jwt = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.test"
        let input = jwt <> "~"
        case deserializePresentation input of
          Right (SDJWTPresentation parsedJwt parsedDisclosures mbKbJwt) -> do
            parsedJwt `shouldBe` jwt
            length parsedDisclosures `shouldBe` 0
            mbKbJwt `shouldBe` Nothing
          Left err -> expectationFailure $ "Failed to deserialize: " ++ show err
      
      it "handles empty input (parses as empty JWT)" $ do
        case deserializePresentation "" of
          Right (SDJWTPresentation parsedJwt parsedDisclosures mbKbJwt) -> do
            parsedJwt `shouldBe` ""
            length parsedDisclosures `shouldBe` 0
            mbKbJwt `shouldBe` Nothing
          Left _ -> return ()  -- Empty JWT might be rejected by validation
      
      it "handles JWT only (no tilde)" $ do
        let jwt = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.test"
        case deserializePresentation jwt of
          Right (SDJWTPresentation parsedJwt parsedDisclosures mbKbJwt) -> do
            parsedJwt `shouldBe` jwt
            length parsedDisclosures `shouldBe` 0
            mbKbJwt `shouldBe` Nothing
          Left err -> expectationFailure $ "Failed to deserialize: " ++ show err