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