packages feed

keiro-dsl-0.18.0.0: test/conformance-refined-base16/Main.hs

module Main (main) where

import Conformance.RefinedBase16.Bindings (contentHashBinding, contentHashFixtures)
import Conformance.RefinedBase16.Domain
import Conformance.RefinedBase16.Historical (historicalContentHashCodec)
import Control.Monad (forM_, unless)
import Data.Aeson (Value (..), parseJSON)
import Data.Aeson qualified as Aeson
import Data.Aeson.Key qualified as Key
import Data.Aeson.KeyMap qualified as KeyMap
import Data.Aeson.Types (parseEither)
import Data.ByteString qualified as BS
import Data.Either (isLeft)
import Data.List.NonEmpty qualified as NonEmpty
import Data.Map.Strict qualified as Map
import Data.Text (Text)
import Data.Text qualified as T
import Generated.RefinedBase16.HashJobs.Queue (HashJob (..), encodeHashJob, parseHashJob)
import Generated.RefinedBase16.HashLookup.QueryContract (HashLookupQueryInput, HashLookupQueryResult)
import Generated.RefinedBase16.HashStore.Codec
import Generated.RefinedBase16.HashStore.Domain
import Generated.RefinedBase16.HashStore.Harness (harnessAssertions)
import Generated.RefinedBase16.HashStore.Transducer (hashStoreTransducer)
import Generated.RefinedBase16.Structural.CodecCompare.ContentHash (compareWithHistorical)
import Generated.RefinedBase16.StructuralConformance (structuralConformanceAssertions)
import Keiki.Core (applyEventsEither, (!))
import Keiro.Codec (EventType (..), eventType)
import Keiro.Codec.Structural (FixtureCases (..), StructuralBinding (..), bindingDomainRoundTrip, bindingShapeRoundTrip)
import Keiro.Dsl.CodecCompare (CompareObservation (..), FixtureVerdict (..), HistoricalCodec (..), classifyObservation, reportSucceeded)
import Keiro.Test.ReplayCompatibility (checkNormalizationLaw)
import System.Exit (exitFailure)

main :: IO ()
main = do
  comparison <-
    compareWithHistorical
      historicalContentHashCodec
      "test/conformance-refined-base16/fixtures/codec-compare"
  let assertions =
        [("structural/" <> label, passed) | (label, passed) <- structuralConformanceAssertions]
          <> harnessAssertions
          <> [ ("lowercase output preserves empty and leading-zero bytes", canonicalWire),
               ("mixed-case and uppercase inputs normalize without changing bytes", historicalSpellingsNormalize),
               ("malformed spellings fail before the consumer binding", malformedRejected),
               ("arbitrary byte lengths remain admitted", unrestrictedLengths),
               ("normalization preserves serialized multi-event replay state", normalizationReplayLaw),
               ("replay-only history preserves the decoded hash", replayOnlyNormalization),
               ("a first event that loses the hash is rejected", headInformationLossRejected),
               ("a transposing consumer binding violates both total laws", transposingBindingRejected),
               ("historical repository codec remains byte-compatible", historicalCodecParity),
               ("generated historical codec comparison succeeds", reportSucceeded comparison),
               ("canonicalization preserves content-derived durable identity", durableIdentityStable),
               ("optional, list, and map compositions round-trip", nestedRoundTrip),
               ("queue payloads preserve refined hashes", queueRoundTrip),
               ("query contracts preserve refined domain types", queryAgreement),
               ("application-owned workflow codecs retain the same bytes", workflowCodecRoundTrip)
             ]
  forM_ assertions $ \(label, passed) ->
    putStrLn ((if passed then "PASS  " else "FAIL  ") <> label)
  unless (all snd assertions) exitFailure

canonicalWire :: Bool
canonicalWire =
  encodeContentHashMapped (hash [0, 175]) == String "00af"
    && encodeContentHashMapped (hash []) == String ""

historicalSpellingsNormalize :: Bool
historicalSpellingsNormalize =
  decodeContentHashMapped (String "00aF") == Right (hash [0, 175])
    && decodeContentHashMapped (String "00AF") == Right (hash [0, 175])
    && fmap encodeContentHashMapped (decodeContentHashMapped (String "00AF")) == Right (String "00af")

malformedRejected :: Bool
malformedRejected =
  all (isLeft . decodeContentHashMapped . String) ["0", "0x00", "gg", "00 af", " 00"]
    && isLeft (decodeContentHashMapped Null)

unrestrictedLengths :: Bool
unrestrictedLengths =
  all
    (\value -> decodeContentHashMapped (encodeContentHashMapped value) == Right value)
    [hash [], hash [0], hash [0, 17, 34, 51, 68], hash [0 .. 31], hash [0 .. 32]]

normalizationReplayLaw :: Bool
normalizationReplayLaw =
  null
    ( checkNormalizationLaw
        decodeContentHashMapped
        encodeContentHashMapped
        replayRawHashChain
        []
        (String "00AF")
        (String "00af")
        []
    )

replayRawHashChain :: [Value] -> Either Text (HashStoreVertex, ContentHash)
replayRawHashChain wires = do
  events <- concat <$> traverse rawTransition wires
  (vertex, registers) <- firstText (applyEventsEither hashStoreTransducer (HashStoreEmpty, initialHashStoreRegs) events)
  pure (vertex, registers ! #current)
  where
    rawTransition wire = do
      let value = hash []
          optional = MaybeContentHash Nothing
          envelope = sampleEnvelope
          stored = HashStored (HashStoredData value optional envelope)
          audited = HashAudited (HashAuditedData value optional envelope)
      storedEvent <- parseHashStoreEvent (EventType "HashStored") (replaceObjectField "hash" wire (encodeHashStoreEvent stored))
      auditedEvent <- parseHashStoreEvent (EventType "HashAudited") (replaceObjectField "hash" wire (encodeHashStoreEvent audited))
      pure [storedEvent, auditedEvent]

replayOnlyNormalization :: Bool
replayOnlyNormalization =
  case parseHashStoreEvent (EventType "LegacyHashImported") rawEvent of
    Left _ -> False
    Right parsedEvent ->
      case applyEventsEither hashStoreTransducer (HashStoreEmpty, initialHashStoreRegs) [parsedEvent] of
        Left _ -> False
        Right (vertex, registers) ->
          vertex == HashStoreStored && registers ! #current == hash [0, 175]
  where
    event = LegacyHashImported (LegacyHashImportedData (hash []) (MaybeContentHash Nothing) sampleEnvelope)
    rawEvent = replaceObjectField "hash" (String "00AF") (encodeHashStoreEvent event)

headInformationLossRejected :: Bool
headInformationLossRejected =
  isLeft (parseHashStoreEvent kind (deleteObjectField "hash" encoded))
  where
    event = HashStored (HashStoredData (hash [0, 175]) (MaybeContentHash Nothing) sampleEnvelope)
    kind = eventType hashStoreCodec event
    encoded = encodeHashStoreEvent event

transposingBindingRejected :: Bool
transposingBindingRejected =
  bindingDomainRoundTrip contentHashBinding value
    && bindingShapeRoundTrip contentHashBinding (BS.pack [0, 175])
    && not (bindingDomainRoundTrip brokenBinding value)
    && not (bindingShapeRoundTrip brokenBinding (BS.pack [0, 175]))
  where
    value = hash [0, 175]
    brokenBinding =
      StructuralBinding
        { bindingToShape = \(ContentHash bytes) -> BS.reverse bytes,
          bindingFromShape = ContentHash
        }

historicalCodecParity :: Bool
historicalCodecParity =
  all
    ( \(label, value) ->
        classifyObservation
          (EncodeObservation label (historicalContentHashCodec.encode value) (encodeContentHashMapped value))
          == Right JsonParity
    )
    (NonEmpty.toList (fixtureCases contentHashFixtures))
    && historicalContentHashCodec.decode (String "00AF") == Right (hash [0, 175])

durableIdentityStable :: Bool
durableIdentityStable =
  case historicalContentHashCodec.decode (String "00AF") of
    Left _ -> False
    Right historical ->
      case decodeContentHashMapped (String "00af") of
        Left _ -> False
        Right candidate -> durableIdentity historical == durableIdentity candidate

durableIdentity :: ContentHash -> Word
durableIdentity (ContentHash bytes) = BS.foldl' (\acc byte -> acc * 16777619 + fromIntegral byte) 2166136261 bytes

nestedRoundTrip :: Bool
nestedRoundTrip = decodeHashEnvelopeMapped (encodeHashEnvelopeMapped sampleEnvelope) == Right sampleEnvelope

queueRoundTrip :: Bool
queueRoundTrip = parseHashJob (encodeHashJob payload) == Right payload
  where
    payload = HashJob (hash [0, 175]) (MaybeContentHash Nothing) sampleEnvelope

queryAgreement :: Bool
queryAgreement =
  queryInputIdentity (MaybeContentHash (Just (hash [0, 175]))) == MaybeContentHash (Just (hash [0, 175]))
    && queryResultIdentity sampleEnvelope == sampleEnvelope

queryInputIdentity :: HashLookupQueryInput -> MaybeContentHash
queryInputIdentity = id

queryResultIdentity :: HashLookupQueryResult -> HashEnvelope
queryResultIdentity = id

workflowCodecRoundTrip :: Bool
workflowCodecRoundTrip =
  parseEither parseJSON (Aeson.toJSON value) == Right value
    && parseEither parseJSON (String "00AF") == Right value
  where
    value = hash [0, 175]

sampleEnvelope :: HashEnvelope
sampleEnvelope =
  HashEnvelope
    (hash [0, 175])
    (Just (hash [255]))
    (MaybeContentHash (Just (hash [0, 17, 34, 51, 68])))
    [hash [], hash [1], hash [2, 3, 4]]
    (Map.fromList [("short", hash [0]), ("longer", hash [0 .. 7])])

hash :: [Word] -> ContentHash
hash = ContentHash . BS.pack . map fromIntegral

firstText :: (Show problem) => Either problem value -> Either Text value
firstText = either (Left . T.pack . show) Right

deleteObjectField :: Key.Key -> Value -> Value
deleteObjectField key (Object value) = Object (KeyMap.delete key value)
deleteObjectField _ value = value

replaceObjectField :: Key.Key -> Value -> Value -> Value
replaceObjectField key inserted (Object value) = Object (KeyMap.insert key inserted value)
replaceObjectField _ _ value = value