diff --git a/core/sessions/src/test/kotlin/com/correx/core/sessions/DefaultSessionReducerTest.kt b/core/sessions/src/test/kotlin/com/correx/core/sessions/DefaultSessionReducerTest.kt deleted file mode 100644 index d1d2d337..00000000 --- a/core/sessions/src/test/kotlin/com/correx/core/sessions/DefaultSessionReducerTest.kt +++ /dev/null @@ -1,121 +0,0 @@ -package com.correx.core.sessions - -import com.correx.core.events.events.EventMetadata -import com.correx.core.events.events.OperatorProfileBoundEvent -import com.correx.core.events.events.SessionWorkspaceBoundEvent -import com.correx.core.events.events.StoredEvent -import com.correx.core.events.types.EventId -import com.correx.core.events.types.SessionId -import kotlinx.datetime.Instant -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNull -import org.junit.jupiter.api.Test - -class DefaultSessionReducerTest { - - private val reducer = DefaultSessionReducer() - private val sessionId = SessionId("s-test") - private val initialState = SessionState(status = SessionStatus.ACTIVE) - - private fun stored(payload: com.correx.core.events.events.EventPayload) = StoredEvent( - metadata = EventMetadata( - eventId = EventId("e-1"), - sessionId = sessionId, - timestamp = Instant.parse("2026-01-01T00:00:00Z"), - schemaVersion = 1, - causationId = null, - correlationId = null, - ), - sequence = 1L, - sessionSequence = 1L, - payload = payload, - ) - - @Test - fun `OperatorProfileBoundEvent populates boundProfile`() { - val event = stored( - OperatorProfileBoundEvent( - sessionId = sessionId, - about = "Expert Kotlin engineer", - approvalMode = "auto", - preferredModels = listOf("model-x"), - conventions = listOf("no var"), - ), - ) - - val result = reducer.reduce(initialState, event) - - val profile = result.boundProfile - assertEquals("Expert Kotlin engineer", profile?.about) - assertEquals("auto", profile?.approvalMode) - assertEquals(listOf("model-x"), profile?.preferredModels) - assertEquals(listOf("no var"), profile?.conventions) - } - - @Test - fun `boundProfile is null by default`() { - assertNull(initialState.boundProfile) - } - - @Test - fun `WorkflowFailedEvent flips session status to FAILED`() { - val event = stored( - com.correx.core.events.events.WorkflowFailedEvent( - sessionId = sessionId, - stageId = com.correx.core.events.types.StageId("define_styling_tokens"), - reason = "did not produce declared artifacts", - retryExhausted = true, - ), - ) - assertEquals(SessionStatus.FAILED, reducer.reduce(initialState, event).status) - } - - @Test - fun `WorkflowCompletedEvent flips session status to COMPLETED`() { - val event = stored( - com.correx.core.events.events.WorkflowCompletedEvent( - sessionId = sessionId, - terminalStageId = com.correx.core.events.types.StageId("report"), - totalStages = 3, - workflowId = "review_loop", - ), - ) - assertEquals(SessionStatus.COMPLETED, reducer.reduce(initialState, event).status) - } - - @Test - fun `WorkflowCompletedEvent for freestyle planning stays ACTIVE (hands off to execution)`() { - val event = stored( - com.correx.core.events.events.WorkflowCompletedEvent( - sessionId = sessionId, - terminalStageId = com.correx.core.events.types.StageId("architect"), - totalStages = 2, - workflowId = "freestyle_planning", - ), - ) - assertEquals(SessionStatus.ACTIVE, reducer.reduce(initialState, event).status) - } - - @Test - fun `unrelated event leaves boundProfile unchanged`() { - val state = initialState.copy( - boundProfile = BoundProfile( - about = "original", - approvalMode = "", - preferredModels = emptyList(), - conventions = emptyList(), - ), - ) - val event = stored( - SessionWorkspaceBoundEvent( - sessionId = sessionId, - workspaceRoot = "/ws", - allowedPaths = listOf("/ws"), - ), - ) - - val result = reducer.reduce(state, event) - - assertEquals("original", result.boundProfile?.about) - } -} diff --git a/testing/projections/src/test/kotlin/DefaultSessionReducerTest.kt b/testing/projections/src/test/kotlin/DefaultSessionReducerTest.kt index 872f8697..9e272eab 100644 --- a/testing/projections/src/test/kotlin/DefaultSessionReducerTest.kt +++ b/testing/projections/src/test/kotlin/DefaultSessionReducerTest.kt @@ -11,8 +11,10 @@ import com.correx.core.events.types.SessionId import com.correx.core.events.types.StageId import com.correx.core.events.types.TransitionId import com.correx.core.events.events.AgentInstructionsBoundEvent +import com.correx.core.events.events.OperatorProfileBoundEvent import com.correx.core.events.events.ProjectProfileBoundEvent import com.correx.core.sessions.BoundAgentInstructions +import com.correx.core.sessions.BoundProfile import com.correx.core.sessions.BoundProjectProfile import com.correx.core.sessions.BoundWorkspace import com.correx.core.sessions.DefaultSessionReducer @@ -378,6 +380,72 @@ class DefaultSessionReducerTest { status = SessionStatus.CREATED ) + @Test + fun `OperatorProfileBoundEvent populates boundProfile`() { + val result = reducer.reduce( + state = activeState(), + event = stored( + sessionId = sessionId, + payload = OperatorProfileBoundEvent( + sessionId = sessionId, + about = "Expert Kotlin engineer", + approvalMode = "auto", + preferredModels = listOf("model-x"), + conventions = listOf("no var"), + ), + ), + ) + + val profile = result.boundProfile + assertEquals("Expert Kotlin engineer", profile?.about) + assertEquals("auto", profile?.approvalMode) + assertEquals(listOf("model-x"), profile?.preferredModels) + assertEquals(listOf("no var"), profile?.conventions) + } + + @Test + fun `unrelated event leaves boundProfile unchanged`() { + val state = activeState().copy( + boundProfile = BoundProfile( + about = "original", + approvalMode = "", + preferredModels = emptyList(), + conventions = emptyList(), + ), + ) + val result = reducer.reduce( + state = state, + event = stored( + sessionId = sessionId, + payload = SessionWorkspaceBoundEvent( + sessionId = sessionId, + workspaceRoot = "/ws", + allowedPaths = listOf("/ws"), + ), + ), + ) + + assertEquals("original", result.boundProfile?.about) + } + + @Test + fun `WorkflowCompletedEvent for freestyle planning stays ACTIVE (hands off to execution)`() { + val result = reducer.reduce( + state = activeState(), + event = stored( + sessionId = sessionId, + payload = WorkflowCompletedEvent( + sessionId, + terminalStageId = StageId("architect"), + totalStages = 2, + workflowId = "freestyle_planning", + ), + ), + ) + + assertEquals(SessionStatus.ACTIVE, result.status) + } + private fun activeState() = SessionState( status = SessionStatus.ACTIVE