7936251d6b
P2-1: Guard SessionId NPE with safe-call in SessionsReducer
P2-2: Remove 16 dead event classes + reducer branches; replace with live orchestration events in tests
P2-3: Standardize ChatInput divergences — guard CancelSession, single-arg ProtocolError
P2-4: Remove dead TuiToolRecord.diff and ToolDisplayStatus.REQUESTED
P2-5: Remove dead streamLive from SessionEventBridge
P2-6: Extract SessionsReducerContext data class for 6+ param method
P2-7: Replace StageId("none") sentinel with TypeId.NONE
P2-9: Add TypeId.random() factory on all type-alias IDs
P2-10: Add KDoc on schemaVersion documenting reserved-for-migration
P2-8: Verified RouterReducer string-template already correct (TypeId value class toString)
64 lines
2.3 KiB
Kotlin
64 lines
2.3 KiB
Kotlin
import com.correx.core.events.events.EventMetadata
|
|
import com.correx.core.events.events.NewEvent
|
|
import com.correx.core.events.events.OrchestrationPausedEvent
|
|
import com.correx.core.events.events.OrchestrationResumedEvent
|
|
import com.correx.core.events.events.WorkflowStartedEvent
|
|
import com.correx.core.events.stores.EventStore
|
|
import com.correx.core.events.types.EventId
|
|
import com.correx.core.events.types.SessionId
|
|
import com.correx.core.events.types.StageId
|
|
import com.correx.core.sessions.DefaultSessionReducer
|
|
import com.correx.core.sessions.SessionProjector
|
|
import com.correx.core.sessions.projections.replay.DefaultEventReplayer
|
|
import com.correx.infrastructure.persistence.InMemoryEventStore
|
|
import kotlinx.coroutines.runBlocking
|
|
import kotlinx.datetime.Clock
|
|
import org.junit.jupiter.api.Assertions.assertEquals
|
|
import org.junit.jupiter.api.Test
|
|
|
|
class SessionReplayDeterminismTest {
|
|
|
|
private fun build(store: EventStore) = DefaultEventReplayer(
|
|
store,
|
|
SessionProjector(DefaultSessionReducer()),
|
|
)
|
|
|
|
@Test
|
|
fun `same events produce same state`(): Unit = runBlocking {
|
|
val sessionId = SessionId("s1")
|
|
|
|
val store1 = InMemoryEventStore()
|
|
val store2 = InMemoryEventStore()
|
|
|
|
val events = mapOf(
|
|
EventMetadata(EventId("start"), sessionId, Clock.System.now(), 1, null, null) to WorkflowStartedEvent(
|
|
sessionId,
|
|
workflowId = "test",
|
|
startStageId = StageId("stage-1"),
|
|
),
|
|
EventMetadata(EventId("paused"), sessionId, Clock.System.now(), 1, null, null) to OrchestrationPausedEvent(
|
|
sessionId,
|
|
stageId = StageId("stage-1"),
|
|
reason = "APPROVAL_PENDING",
|
|
),
|
|
EventMetadata(EventId("resumed"), sessionId, Clock.System.now(), 1, null, null) to OrchestrationResumedEvent(
|
|
sessionId,
|
|
stageId = StageId("stage-1"),
|
|
),
|
|
)
|
|
|
|
events.forEach { (meta, payload) ->
|
|
store1.append(NewEvent(meta, payload))
|
|
store2.append(NewEvent(meta, payload))
|
|
}
|
|
|
|
val replayer1 = build(store1)
|
|
val replayer2 = build(store2)
|
|
|
|
val state1 = replayer1.rebuild(sessionId)
|
|
val state2 = replayer2.rebuild(sessionId)
|
|
|
|
assertEquals(state1, state2)
|
|
}
|
|
}
|