import com.correx.core.events.events.ChatSessionStartedEvent import com.correx.core.events.events.StoredEvent import com.correx.core.events.events.ToolInvokedEvent import com.correx.core.events.serialization.eventJson import com.correx.core.events.types.SessionId import com.correx.core.sessions.projections.Projection import com.correx.testing.fixtures.DeterministicHarness import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test class DeterministicHarnessTest { private data class SimpleState(val eventCount: Int = 0) private val simpleProjection = object : Projection { override fun initial() = SimpleState() override fun apply(state: SimpleState, event: StoredEvent) = state.copy(eventCount = state.eventCount + 1) } @Test fun `two harnesses with same payloads yield identical events`() = runBlocking { val harness1 = DeterministicHarness() val harness2 = DeterministicHarness() val sessionId = SessionId("test-session") val payload1 = ChatSessionStartedEvent(sessionId) val payload2 = ToolInvokedEvent("read_file") harness1.givenEvents(sessionId, payload1, payload2) harness2.givenEvents(sessionId, payload1, payload2) val events1 = harness1.eventStore.read(sessionId) val events2 = harness2.eventStore.read(sessionId) assertEquals(events1.size, events2.size) events1.zip(events2).forEach { (e1, e2) -> assertEquals(e1.metadata.eventId, e2.metadata.eventId, "eventIds must match") assertEquals(e1.metadata.timestamp, e2.metadata.timestamp, "timestamps must match") assertEquals(e1.sequence, e2.sequence, "sequences must match") assertEquals(e1.sessionSequence, e2.sessionSequence, "sessionSequences must match") assertEquals( eventJson.encodeToString(StoredEvent.serializer(), e1), eventJson.encodeToString(StoredEvent.serializer(), e2), "stored event JSON must be byte-identical", ) } } @Test fun `rebuild applies events deterministically`() = runBlocking { val harness = DeterministicHarness() val sessionId = SessionId("rebuild-test") harness.givenEvents( sessionId, ChatSessionStartedEvent(sessionId), ToolInvokedEvent("read_file"), ToolInvokedEvent("write_file") ) val state = harness.rebuild(sessionId, simpleProjection) assertEquals(3, state.eventCount) } @Test fun `clock is fixed for deterministic timestamps`(): Unit = runBlocking { val harness = DeterministicHarness() val sessionId = SessionId("clock-test") harness.givenEvents(sessionId, ChatSessionStartedEvent(sessionId)) val event = harness.eventStore.read(sessionId).first() assertEquals("2026-01-01T00:00:00Z", event.metadata.timestamp.toString()) } }