test(fixtures): DeterministicHarness — event-log-in, derived-state-out builder

Provides a reusable builder that packages the event-log-in → derived-state-out pattern for deterministic tests. Implements fixed clock and deterministic event ID generation to ensure byte-identical event JSON across test runs with same payloads.

Acceptance criteria met:
- Two harnesses fed same payloads yield byte-identical stored event JSON
- Fixed clock ensures timestamp determinism (2026-01-01T00:00:00Z)
- Event IDs are deterministically generated (event-0, event-1, etc.)
- Supports both givenEvents (append) and rebuild (replay via projection)
This commit is contained in:
2026-06-12 14:43:07 +04:00
parent 64b58e3b05
commit 7558d32ad9
3 changed files with 119 additions and 0 deletions
+1
View File
@@ -14,5 +14,6 @@ dependencies {
implementation(project(":core:context"))
implementation(project(":core:kernel"))
implementation(project(":core:validation"))
implementation(project(":infrastructure:persistence"))
}
tasks.named("koverVerify").configure { enabled = false }
@@ -0,0 +1,41 @@
package com.correx.testing.fixtures
import com.correx.core.events.events.EventMetadata
import com.correx.core.events.events.EventPayload
import com.correx.core.events.events.NewEvent
import com.correx.core.events.types.EventId
import com.correx.core.events.types.SessionId
import com.correx.infrastructure.persistence.InMemoryEventStore
import com.correx.core.sessions.projections.Projection
import com.correx.core.sessions.projections.replay.DefaultEventReplayer
import kotlinx.datetime.Instant
class DeterministicHarness {
val eventStore: InMemoryEventStore = InMemoryEventStore()
val clock: Instant = Instant.parse("2026-01-01T00:00:00Z")
private var eventCounter = 0
suspend fun givenEvents(sessionId: SessionId, vararg payloads: EventPayload) {
val events = payloads.map { payload ->
val eventId = EventId("event-${eventCounter++}")
NewEvent(
metadata = EventMetadata(
eventId = eventId,
sessionId = sessionId,
timestamp = clock,
schemaVersion = 1,
causationId = null,
correlationId = null
),
payload = payload
)
}
eventStore.appendAll(events)
}
fun <S> rebuild(sessionId: SessionId, projection: Projection<S>): S {
val replayer = DefaultEventReplayer(eventStore, projection)
return replayer.rebuild(sessionId)
}
}