packages feed

kiroku-store-0.3.0.0: test/Test/StreamNameLookup.hs

{- | Tests for 'Kiroku.Store.Read.lookupStreamNames' and 'lookupStreamName'
(plan 36). These resolve the surrogate 'RecordedEvent.originalStreamId' carried
by fan-in reads back to a human-readable 'StreamName', without every read having
to return the name (which a benchmark showed costs ~13% on @$all@ pages).
-}
module Test.StreamNameLookup (spec) where

import Control.Lens ((^.))
import Data.Aeson qualified as Aeson
import Data.Generics.Labels ()
import Data.IORef (modifyIORef', newIORef, readIORef)
import Data.Map.Strict qualified as Map
import Data.Vector qualified as V
import Kiroku.Store
import Test.Helpers (makeEvent, waitForPublisher, withTestStore, withTestStoreSettings)
import Test.Hspec

spec :: Spec
spec = describe "lookupStreamNames / lookupStreamName" $ do
    around withTestStore $ do
        it "resolves originalStreamId back to the source stream for $all reads" $ \store -> do
            Right _ <-
                runStoreIO store $
                    appendToStream (StreamName "orders-1") NoStream [makeEvent "OrderCreated" (Aeson.object [])]
            Right _ <-
                runStoreIO store $
                    appendToStream (StreamName "shipments-1") NoStream [makeEvent "ShipmentDispatched" (Aeson.object [])]
            Right evs <- runStoreIO store $ readAllForward (GlobalPosition 0) 100
            Right names <- runStoreIO store $ lookupStreamNames (map (^. #originalStreamId) (V.toList evs))
            let nameOf typ =
                    case V.find ((== EventType typ) . (^. #eventType)) evs of
                        Just e -> Map.lookup (e ^. #originalStreamId) names
                        Nothing -> Nothing
            nameOf "OrderCreated" `shouldBe` Just (StreamName "orders-1")
            nameOf "ShipmentDispatched" `shouldBe` Just (StreamName "shipments-1")

        it "omits ids that name no existing stream" $ \store -> do
            Right names <- runStoreIO store $ lookupStreamNames [StreamId 999999]
            Map.size names `shouldBe` 0

        it "returns an empty map for an empty id list" $ \store -> do
            Right names <- runStoreIO store $ lookupStreamNames []
            Map.null names `shouldBe` True

        it "lookupStreamName resolves a single id, and Nothing for an unknown one" $ \store -> do
            Right _ <-
                runStoreIO store $
                    appendToStream (StreamName "widgets-7") NoStream [makeEvent "WidgetMade" (Aeson.object [])]
            Right (Just sid) <- runStoreIO store $ lookupStreamId (StreamName "widgets-7")
            Right found <- runStoreIO store $ lookupStreamName sid
            found `shouldBe` Just (StreamName "widgets-7")
            Right missing <- runStoreIO store $ lookupStreamName (StreamId 888888)
            missing `shouldBe` Nothing

    it "short-circuits empty input without a pool checkout" $ do
        ref <- newIORef (0 :: Int)
        let handler (ConnectionObservation _ InUseConnectionStatus) =
                modifyIORef' ref (+ 1)
            handler _ =
                pure ()
        withTestStoreSettings (\settings -> settings{observationHandler = Just handler}) $ \store -> do
            Right appendResult <-
                runStoreIO store $
                    appendToStream (StreamName "lookup-count-1") NoStream [makeEvent "LookupCounted" (Aeson.object [])]
            waitForPublisher store (appendResult ^. #globalPosition)
            Right (Just sid) <- runStoreIO store $ lookupStreamId (StreamName "lookup-count-1")

            beforeEmpty <- readIORef ref
            Right emptyNames <- runStoreIO store $ lookupStreamNames []
            afterEmpty <- readIORef ref
            emptyNames `shouldBe` Map.empty
            afterEmpty - beforeEmpty `shouldBe` 0

            beforeReal <- readIORef ref
            Right names <- runStoreIO store $ lookupStreamNames [sid]
            afterReal <- readIORef ref
            names `shouldBe` Map.singleton sid (StreamName "lookup-count-1")
            afterReal - beforeReal `shouldBe` 1