packages feed

keiro-dsl-0.12.0.0: test/conformance-projection-catalog/Main.hs

module Main (main) where

import CatalogDemo.MappedDomain (QualificationPayload (..), QualificationResult (..), QueryCriteria (..), QueueMetadata (..), SharedReference (..))
import Control.Monad (forM_, unless)
import Data.Aeson (Value (..), object, (.=))
import Data.Aeson.KeyMap qualified as KeyMap
import Data.Text (Text)
import Generated.CatalogDemo.CatalogAudit.ReadModelHarness qualified as CatalogAudit
import Generated.CatalogDemo.OrderInline.QueryContract (OrderInlineQueryInput, OrderInlineQueryResult)
import Generated.CatalogDemo.OrderInline.ReadModelHarness qualified as OrderInline
import Generated.CatalogDemo.OrderTotalsLookup.ReadModelHarness qualified as OrderTotalsLookup
import Generated.CatalogDemo.Orders.Harness qualified as Orders
import Generated.CatalogDemo.ProjectionCatalog
  ( orderSummaryWriterInlineProjections
  , ordersInlineProjections
  , projectionCatalogAsyncRegistrations
  , projectionCatalogInventory
  , projectionCatalogQuerySupplies
  , projectionCatalogRegistrations
  , reportingRebuildGroupId
  , shipmentWriterInlineProjections
  , shipmentsInlineProjections
  , shippingRebuildGroupId
  )
import Generated.CatalogDemo.ShipmentLookup.ReadModelHarness qualified as ShipmentLookup
import Generated.CatalogDemo.Shipments.Harness qualified as Shipments
import Generated.CatalogDemo.QualificationJobs.Queue
import Generated.CatalogDemo.QualificationJobs.QueueCodec (qualificationJobsJobCodec)
import Generated.CatalogDemo.StructuralConformance (structuralConformanceAssertions)
import Keiro.PGMQ.Codec (JobCodec (..))
import Keiro.Projection.Catalog qualified as Catalog
import Kiroku.Store.Subscription.Types (MissingCheckpointPolicy (FromCurrentHead))

main :: IO ()
main = do
  readModelFactsPassed <- and <$> sequence [CatalogAudit.runReadModelFacts, OrderInline.runReadModelFacts, OrderTotalsLookup.runReadModelFacts, ShipmentLookup.runReadModelFacts]
  assert "generated read-model facts" readModelFactsPassed
  let perturbed = map perturbSubscriptionName projectionCatalogAsyncRegistrations
      mutated = CatalogAudit.catalogFactsAgainst projectionCatalogRegistrations perturbed projectionCatalogQuerySupplies
  assert
    "perturbed async registration identity is detected"
    (any (\(fact, expected, actual) -> fact == "asyncRegistration:audit_writer" && expected /= actual) mutated)
  let mutatedReadModelFacts factName =
        [ if fact == factName then (fact, expected, actual <> "-WRONG") else row
        | row@(fact, expected, actual) <- CatalogAudit.readModelFacts
        ]
      mutationFails factName =
        any (\(fact, expected, actual) -> fact == factName && expected /= actual) (mutatedReadModelFacts factName)
  assert "freshness fact mutation is detected" (mutationFails "freshness")
  assert "cursor fact mutation is detected" (mutationFails "cursorAuthority")
  assert "delivery fact mutation is detected" (mutationFails "projectionDelivery")
  mapM_ (uncurry assert) Orders.harnessAssertions
  mapM_ (uncurry assert) Shipments.harnessAssertions
  mapM_ (uncurry assert) structuralConformanceAssertions
  let queryInput :: OrderInlineQueryInput
      queryInput = QueryCriteria "qualification-query"
      queryResult :: OrderInlineQueryResult
      queryResult = Just (QualificationResult "qualified-result")
  assert "non-unit typed query contract" (queryCriteriaText queryInput == "qualification-query" && queryResult == Just (QualificationResult "qualified-result"))
  exerciseQualificationQueue
  let targets = Catalog.inventoryTargets projectionCatalogInventory
      groups = Catalog.inventoryGroups projectionCatalogInventory
      sourceFingerprints = map inventoryCodecFingerprint (Catalog.inventorySources projectionCatalogInventory)
  assert "four physical targets" (length targets == 4)
  assert
    "mixed clear/preserve reset policy"
    ( length (filter ((== Catalog.ClearBeforeReplay) . inventoryResetPolicy) targets) == 2
        && length (filter ((== Catalog.PreserveAndReconcile) . inventoryResetPolicy) targets) == 2
    )
  assert "one target dependency" (length (filter (not . null . inventoryDependsOn) targets) == 1)
  assert "two atomic ordered groups" (map (length . inventoryOrderedTargets) groups == [3, 1])
  assert
    "projection source fingerprints"
    ( sourceFingerprints
        == [ "aggregate:Orders/generated-codec/v1/mapped-132056a8f2ee095d",
             "aggregate:Shipments/generated-codec/v1/mapped-9456a95e380c74b5",
             "category:audit/application-decoder/v1"
           ]
    )
  assert "three generated owners" (length (Catalog.inventoryProjections projectionCatalogInventory) == 3)
  assert
    "v1/v2 projection revision bridge"
    ( map (Catalog.projectionRevisionIdText . inventoryRevisionId) (Catalog.inventoryProjectionRevisions projectionCatalogInventory)
        == ["reporting_v1", "reporting_v2"]
    )
  assert "two typed aggregate handlers" (length orderSummaryWriterInlineProjections == 1 && length shipmentWriterInlineProjections == 1)
  assert "source-selected inline handlers stay singular" (length ordersInlineProjections == 1 && length shipmentsInlineProjections == 1)
  assert "four query registrations" (length projectionCatalogRegistrations == 4)
  assert
    "one owner supplies both order queries"
    ( map (Catalog.projectionIdText . Catalog.resolvedProjectionId) projectionCatalogQuerySupplies
        == ["audit_writer", "order_summary_writer", "order_summary_writer", "shipment_writer"]
    )
  assert "one async registration" (length projectionCatalogAsyncRegistrations == 1)
  assert "generated missing-checkpoint policy" (map inventoryCheckpointPolicy (Catalog.inventorySubscriptions projectionCatalogInventory) == [FromCurrentHead])
  assert "catalog-scoped rebuild group" (Catalog.rebuildGroupIdText reportingRebuildGroupId == "reporting")
  assert "disjoint catalog rebuild group" (Catalog.rebuildGroupIdText shippingRebuildGroupId == "shipping")
  putStrLn "projection catalog conformance: PASS"

perturbSubscriptionName :: Catalog.AsyncProjectionRegistration -> Catalog.AsyncProjectionRegistration
perturbSubscriptionName (Catalog.AsyncProjectionRegistration projectionId projectionName subscriptionId _ checkpointOnMissing dedupKeyId dedupName) =
  Catalog.AsyncProjectionRegistration projectionId projectionName subscriptionId "catalog-demo-audit-WRONG" checkpointOnMissing dedupKeyId dedupName

inventoryRevisionId :: Catalog.InventoryProjectionRevision -> Catalog.ProjectionRevisionId
inventoryRevisionId (Catalog.InventoryProjectionRevision revisionId _ _ _ _ _ _) = revisionId

exerciseQualificationQueue :: IO ()
exerciseQualificationQueue = do
  let payload =
        QualificationJob
          (SharedReference "shared-queue")
          (QualificationPayload "qualification-7" Nothing)
          (QueueMetadata "metadata-7")
          Nothing
          2
          (object ["trace_id" .= ("trace-7" :: Text)])
      encoded = encodeQualificationJob payload
      expected =
        object
          [ "shared_reference" .= SharedReference "shared-queue",
            "payload"
              .= object
                [ "qualification_id" .= ("qualification-7" :: Text),
                  "note" .= Null
                ],
            "metadata" .= QueueMetadata "metadata-7",
            "maybe_metadata" .= Null,
            "attempt" .= (2 :: Int),
            "trace" .= object ["trace_id" .= ("trace-7" :: Text)]
          ]
      envelope =
        object
          [ "v" .= (1 :: Int),
            "t" .= ("QualificationJob" :: Text),
            "data" .= expected
          ]
      missingRequired = case encoded of
        Object fields -> parseQualificationJob (Object (KeyMap.delete "payload" fields))
        _ -> error "qualification queue encoder did not produce an object"
      checks =
        [ ("qualification queue exact payload", encoded == expected),
          ("qualification queue structural and opaque round-trip", parseQualificationJob encoded == Right payload),
          ("qualification queue required key rejects omission", isLeft missingRequired),
          ("qualification queue present null admits Optional", (maybeMetadata <$> parseQualificationJob encoded) == Right Nothing),
          ("qualification queue schema-v1 envelope", encodeJob qualificationJobsJobCodec payload == envelope && decodeJob qualificationJobsJobCodec envelope == Right payload),
          ("qualification queue physical identity", queuePhysical == "catalog_demo_qualification_jobs")
        ]
  forM_ checks (uncurry assert)

assert :: String -> Bool -> IO ()
assert label condition = unless condition (error ("projection catalog conformance failed: " <> label))

isLeft :: Either problem value -> Bool
isLeft (Left _) = True
isLeft (Right _) = False

inventoryResetPolicy :: Catalog.InventoryTarget -> Catalog.TargetResetPolicy
inventoryResetPolicy Catalog.InventoryTarget {resetPolicy = policy} = policy

inventoryDependsOn :: Catalog.InventoryTarget -> [Catalog.TargetId]
inventoryDependsOn Catalog.InventoryTarget {dependsOn = dependencies} = dependencies

inventoryOrderedTargets :: Catalog.InventoryGroup -> [Catalog.TargetId]
inventoryOrderedTargets Catalog.InventoryGroup {orderedTargets = targets} = targets

inventoryCodecFingerprint :: Catalog.InventorySource -> Text
inventoryCodecFingerprint (Catalog.InventorySource _ _ fingerprint) = fingerprint

inventoryCheckpointPolicy :: Catalog.InventorySubscription -> MissingCheckpointPolicy
inventoryCheckpointPolicy (Catalog.InventorySubscription _ _ _ policy) = policy