Files
correx/testing/projections/src/test/kotlin/InferenceReducerTest.kt
T
kami 219e2c762e feat(cas): content-addressed artifact store (steps 1–8)
New core:artifacts-store interface + infrastructure/artifacts-cas
implementation: segment files + SQLite index, Blake3 hashing,
group-commit fsync via flushBefore, recovery tail-scan, manual
compactor, and oldest-first disk-cap evictor.

Inference events now carry promptArtifactId / responseArtifactId;
orchestrators put bytes before emitting. SqliteEventStore wraps
its txn in artifactStore.flushBefore so segment data is fsynced
before the event commit, making the crash window non-corrupting
(TailScanner re-indexes orphan tail records on reopen).

Compactor and evictor are mutually exclusive via maintenanceMutex.
Step 9 (cloud sync) deferred to a later epic.

See docs/reviews/2026-05-18-cas-steps-1-8-review.md for the final
review.
2026-05-18 12:22:38 +04:00

191 lines
7.1 KiB
Kotlin

import com.correx.core.events.events.InferenceCompletedEvent
import com.correx.core.events.events.InferenceFailedEvent
import com.correx.core.events.events.InferenceStartedEvent
import com.correx.core.events.events.InferenceTimeoutEvent
import com.correx.core.events.events.ModelLoadedEvent
import com.correx.core.events.types.InferenceRequestId
import com.correx.core.events.types.ProviderId
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId
import com.correx.core.inference.DefaultInferenceReducer
import com.correx.core.inference.InferenceRecord
import com.correx.core.inference.InferenceReducer
import com.correx.core.inference.InferenceState
import com.correx.core.inference.InferenceStatus
import com.correx.core.inference.TokenUsage
import com.correx.core.utils.TypeId
import com.correx.testing.fixtures.EventFixtures.stored
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
class InferenceReducerTest {
private lateinit var reducer: InferenceReducer
@BeforeEach
fun setup() {
reducer = DefaultInferenceReducer()
}
@Test
fun `should start a new record on InferenceStartedEvent`() {
val initialState = InferenceState(emptyList())
val event = stored(
payload = InferenceStartedEvent(
requestId = InferenceRequestId("req1"),
sessionId = SessionId("sess1"),
stageId = StageId("stageA"),
providerId = ProviderId("providerX"),
promptArtifactId = TypeId("00".repeat(32)),
),
)
val newState = reducer.reduce(initialState, event)
assertEquals(1, newState.records.size)
val record = newState.records.first()
assertEquals(InferenceStatus.STARTED, record.status)
assertEquals("req1", record.requestId.value)
}
@Test
fun `should complete a record on InferenceCompletedEvent`() {
val startedRecord = InferenceRecord(
requestId = InferenceRequestId("req1"),
sessionId = SessionId("sess1"),
stageId = StageId("stageA"),
providerId = ProviderId("providerX"),
status = InferenceStatus.STARTED,
)
val initialState = InferenceState(listOf(startedRecord))
val event = stored(
payload = InferenceCompletedEvent(
requestId = InferenceRequestId("req1"),
sessionId = SessionId("sess1"),
stageId = StageId("stageA"),
providerId = ProviderId("providerX"),
tokensUsed = TokenUsage(50, 50),
latencyMs = 500L,
responseArtifactId = TypeId("00".repeat(32)),
),
)
val newState = reducer.reduce(initialState, event)
assertEquals(1, newState.records.size)
val record = newState.records.first()
assertEquals(InferenceStatus.COMPLETED, record.status)
assertEquals(100, record.tokensUsed?.totalTokens)
assertEquals(500L, record.latencyMs)
}
@Test
fun `should fail a record on InferenceFailedEvent`() {
val startedRecord = InferenceRecord(
requestId = InferenceRequestId("req1"),
sessionId = SessionId("sess1"),
stageId = StageId("stageA"),
providerId = ProviderId("providerX"),
status = InferenceStatus.STARTED,
)
val initialState = InferenceState(listOf(startedRecord))
val event = stored(
payload = InferenceFailedEvent(
requestId = InferenceRequestId("req1"),
sessionId = SessionId("sess1"),
stageId = StageId("stageA"),
providerId = ProviderId("providerX"),
reason = "Network failure",
),
)
val newState = reducer.reduce(initialState, event)
assertEquals(1, newState.records.size)
val record = newState.records.first()
assertEquals(InferenceStatus.FAILED, record.status)
assertEquals("Network failure", record.failureReason)
}
@Test
fun `should timeout a record on InferenceTimeoutEvent`() {
val startedRecord = InferenceRecord(
requestId = InferenceRequestId("req1"),
sessionId = SessionId("sess1"),
stageId = StageId("stageA"),
providerId = ProviderId("providerX"),
status = InferenceStatus.STARTED,
)
val initialState = InferenceState(listOf(startedRecord))
val event = stored(
payload = InferenceTimeoutEvent(
requestId = InferenceRequestId("req1"),
sessionId = SessionId("sess1"),
stageId = StageId("stageA"),
providerId = ProviderId("providerX"),
timeoutMs = 10000L,
),
)
val newState = reducer.reduce(initialState, event)
assertEquals(1, newState.records.size)
val record = newState.records.first()
assertEquals(InferenceStatus.TIMED_OUT, record.status)
assertEquals(10000L, record.latencyMs)
}
@Test
fun `should pass state unchanged on unrelated events`() {
val startedRecord = InferenceRecord(
requestId = InferenceRequestId("req1"),
sessionId = SessionId("sess1"),
stageId = StageId("stageA"),
providerId = ProviderId("providerX"),
status = InferenceStatus.STARTED,
)
val initialState = InferenceState(listOf(startedRecord))
val event = stored(
payload = ModelLoadedEvent(
sessionId = SessionId("sess1"),
providerId = ProviderId("providerX"),
modelId = "model",
),
)
val newState = reducer.reduce(initialState, event)
assertEquals(1, newState.records.size)
assertEquals(InferenceStatus.STARTED, newState.records.first().status)
}
@Test
fun `should not modify record if requestId does not match`() {
val startedRecord = InferenceRecord(
requestId = InferenceRequestId("req1"),
sessionId = SessionId("sess1"),
stageId = StageId("stageA"),
providerId = ProviderId("providerX"),
status = InferenceStatus.STARTED,
)
val initialState = InferenceState(listOf(startedRecord))
// Event for a different request
val event = stored(
payload = InferenceCompletedEvent(
requestId = InferenceRequestId("req2"),
sessionId = SessionId("sess1"),
stageId = StageId("stageA"),
providerId = ProviderId("providerX"),
tokensUsed = TokenUsage(50, 50),
latencyMs = 500L,
responseArtifactId = TypeId("00".repeat(32)),
),
)
val newState = reducer.reduce(initialState, event)
// The record should remain unchanged
assertEquals(1, newState.records.size)
assertEquals(InferenceStatus.STARTED, newState.records.first().status)
}
}