packages feed

kiroku-store-0.10.0.0: test/Test/UniqueViolationMapping.hs

module Test.UniqueViolationMapping (spec) where

import Control.Monad (forM_)
import Data.Text (Text)
import Data.UUID qualified as UUID
import Hasql.Errors qualified as Errors
import Hasql.Pool (UsageError (..))
import Kiroku.Store.Error
import Kiroku.Store.Types
import Test.Hspec

spec :: Spec
spec = describe "unique violation mapping" $ do
    let eid = EventId (UUID.fromWords 0x01234567 0x89ab7def 0x80123456 0x7890abcd)
        scalar = "Key (event_id)=(01234567-89ab-7def-8012-34567890abcd) already exists."
        composite = "Key (event_id, stream_id)=(01234567-89ab-7def-8012-34567890abcd, 42) already exists."
        fallback = WrongExpectedVersion (StreamName "orders-1") AnyVersion (StreamVersion 0)
        mapAppend = mapUsageError "orders-1" AnyVersion
        cases =
            [ ("events_pkey", scalar, DuplicateEvent (Just eid))
            , ("stream_events_pkey", composite, DuplicateEvent (Just eid))
            , ("ix_streams_stream_name", "Key (stream_name)=(orders-1) already exists.", StreamAlreadyExists (StreamName "orders-1"))
            , ("ux_stream_events_stream_version", "Key (stream_id, stream_version)=(42, 1) already exists.", UnexpectedServerError "23505" "detail-only error")
            , ("unknown_constraint", scalar, fallback)
            ]
    forM_ cases $ \(name, detail, expected) -> do
        it ("classifies the exact message constraint " <> show name) $ do
            let message = constraintMessage name
                outcome = case expected of
                    UnexpectedServerError code _ -> UnexpectedServerError code message
                    other -> other
            mapAppend (serverUsage message (Just detail)) `shouldBe` outcome
        it ("classifies a detail-only constraint " <> show name) $
            mapAppend (serverUsage "detail-only error" (Just ("constraint: " <> name <> "; " <> detail)))
                `shouldBe` expected

    it "does not match stream_events_pkey as events_pkey" $
        mapAppend (serverUsage (constraintMessage "stream_events_pkey") (Just composite))
            `shouldBe` DuplicateEvent (Just eid)
    forM_ ["events_pkey", "stream_events_pkey", "ix_streams_stream_name", "ux_stream_events_stream_version"] $ \name ->
        forM_ ["prefix_" <> name, name <> "_suffix", name <> "$suffix"] $ \other -> do
            it ("rejects the message lookalike " <> show other) $
                mapAppend (serverUsage (constraintMessage other) (Just scalar)) `shouldBe` fallback
            it ("rejects the detail lookalike " <> show other) $
                mapAppend (serverUsage "detail-only error" (Just (other <> "; " <> scalar))) `shouldBe` fallback

    it "uses the message constraint ahead of misleading detail tokens" $
        mapAppend (serverUsage (constraintMessage "ux_stream_events_stream_version") (Just ("events_pkey " <> scalar)))
            `shouldBe` UnexpectedServerError "23505" (constraintMessage "ux_stream_events_stream_version")
    it "retains an unknown message constraint even when detail names a known constraint" $
        mapAppend (serverUsage (constraintMessage "unknown_constraint") (Just ("events_pkey " <> scalar)))
            `shouldBe` fallback
    forM_ ["events_pkey", "stream_events_pkey"] $ \name ->
        forM_ [Nothing, Just "unparseable localized detail"] $ \detail ->
            it ("does not fabricate an event id for " <> show (name, detail)) $
                mapAppend (serverUsage (constraintMessage name) detail) `shouldBe` DuplicateEvent Nothing
    it "maps a composite duplicate inside a transaction with its event id" $
        mapTransactionUsageError (serverUsage (constraintMessage "stream_events_pkey") (Just composite))
            `shouldBe` DuplicateEvent (Just eid)
    it "preserves the link-specific duplicate classification" $
        mapLinkUsageError (StreamName "orders-1") (serverUsage (constraintMessage "stream_events_pkey") (Just composite))
            `shouldBe` EventAlreadyLinked (StreamName "orders-1") (Just eid)
    it "accepts a quoted constraint in legacy detail" $
        mapAppend (serverUsage "detail-only error" (Just (constraintMessage "stream_events_pkey" <> "; " <> composite)))
            `shouldBe` DuplicateEvent (Just eid)
    it "keeps an unnamed unique violation on the conservative fallback" $
        mapAppend (serverUsage "localized message" Nothing) `shouldBe` fallback
    it "does not classify a transaction constraint lookalike as a duplicate" $
        mapTransactionUsageError (serverUsage (constraintMessage "other_events_pkey") (Just scalar))
            `shouldBe` UnexpectedServerError "23505" (constraintMessage "other_events_pkey")
    it "does not classify a link constraint lookalike as already linked" $
        mapLinkUsageError (StreamName "orders-1") (serverUsage (constraintMessage "stream_events_pkey_backup") (Just composite))
            `shouldBe` UnexpectedServerError "23505" (constraintMessage "stream_events_pkey_backup")
    it "attributes a multi-stream name violation to the named operation" $
        attributeMultiStreamError
            [(StreamName "orders-1", AnyVersion), (StreamName "orders-2", NoStream)]
            (serverUsage (constraintMessage "ix_streams_stream_name") (Just "Key (stream_name)=(orders-2) already exists."))
            `shouldBe` StreamAlreadyExists (StreamName "orders-2")
    it "does not attribute a multi-stream constraint lookalike to a later operation" $
        attributeMultiStreamError
            [(StreamName "orders-1", AnyVersion), (StreamName "orders-2", NoStream)]
            (serverUsage (constraintMessage "ix_streams_stream_name_backup") (Just "Key (stream_name)=(orders-2) already exists."))
            `shouldBe` fallback

constraintMessage :: Text -> Text
constraintMessage name = "duplicate key value violates unique constraint \"" <> name <> "\""

serverUsage :: Text -> Maybe Text -> UsageError
serverUsage message detail =
    SessionUsageError $
        Errors.StatementSessionError 1 0 "" [] True $
            Errors.ServerStatementError $
                Errors.ServerError "23505" message detail Nothing Nothing