eventium-core-0.2.1: tests/Eventium/ProjectionSpec.hs
{-# LANGUAGE OverloadedStrings #-}
module Eventium.ProjectionSpec (spec) where
import Control.Exception (evaluate)
import Data.Functor.Contravariant
import Eventium.Codec (Codec (..), DecodeError (..))
import Eventium.Projection
import Eventium.Store.Types
import Test.Hspec
-- Simple test projection: counter that sums integers
counterProjection :: Projection Int Int
counterProjection = Projection 0 (+)
spec :: Spec
spec = do
describe "latestProjection" $ do
it "should compute the correct final state" $ do
latestProjection counterProjection [1, 2, 3, 4] `shouldBe` 10
it "should return the seed for an empty event list" $ do
latestProjection counterProjection [] `shouldBe` 0
describe "allProjections" $ do
it "should return all intermediate states" $ do
allProjections counterProjection [1, 2, 3] `shouldBe` [0, 1, 3, 6]
it "should return just the seed for an empty event list" $ do
allProjections counterProjection [] `shouldBe` [0]
describe "Contravariant instance" $ do
it "should adapt the event type via contramap" $ do
let stringProjection = contramap (length :: String -> Int) counterProjection
latestProjection stringProjection ["hi", "hello", "a"] `shouldBe` 8
describe "projectionMapMaybe" $ do
it "should filter events and apply matching ones" $ do
let evenOnly n = if even n then Just n else Nothing
evenProjection = projectionMapMaybe evenOnly counterProjection
latestProjection evenProjection [1, 2, 3, 4, 5, 6] `shouldBe` 12
it "should return seed when no events match" $ do
let noneMatch _ = Nothing
emptyProjection = projectionMapMaybe noneMatch counterProjection
latestProjection emptyProjection [1, 2, 3] `shouldBe` 0
describe "codecProjection" $ do
it "should deserialize and apply events" $ do
let codec = Codec show (Just . (read :: String -> Int))
codecProj = codecProjection codec counterProjection
latestProjection codecProj ["1", "2", "3"] `shouldBe` 6
it "should error on deserialization failure" $ do
let codec = Codec show (\s -> if s == "bad" then Nothing else Just (read s :: Int))
codecProj = codecProjection codec counterProjection
evaluate (latestProjection codecProj ["1", "bad"]) `shouldThrow` (\(DecodeError ctx _) -> ctx == "codecProjection")
describe "lenientCodecProjection" $ do
it "should apply deserialized events and skip failures" $ do
let codec = Codec show (\s -> if s == "bad" then Nothing else Just (read s :: Int))
codecProj = lenientCodecProjection codec counterProjection
latestProjection codecProj ["1", "2", "bad", "3"] `shouldBe` 6
describe "streamProjectionEventHandler" $ do
it "should update state and position" $ do
let sp = streamProjection "key" (0 :: Int) counterProjection
event = StreamEvent "other" 5 (emptyMetadata "") (10 :: Int)
sp' = streamProjectionEventHandler sp event
sp'.state `shouldBe` 10
sp'.position `shouldBe` 5
sp'.key `shouldBe` "key"