fix(tui): use takeLast(3) for events, add bridge/TUI tests for snapshot recentEvents

- Change EventHistoryStrip take(3) to takeLast(3) so it shows the 3 most
  recent events instead of the 3 oldest (chronological order is oldest-first)
- Add bridge test confirming replaySnapshot populates recentEvents from the
  event store (with 3 different event types mapped correctly)
- Add TUI SessionsReducer test confirming processSessionSnapshotMessage maps
  EventEntryDto entries from the snapshot into TuiEventEntry on the summary
This commit is contained in:
2026-05-26 13:39:11 +04:00
parent 450b492937
commit d701e3cbf3
3 changed files with 62 additions and 1 deletions
@@ -1,5 +1,6 @@
package com.correx.apps.server.bridge
import com.correx.apps.server.protocol.EventEntryDto
import com.correx.apps.server.protocol.ServerMessage
import com.correx.apps.server.registry.WorkflowRegistry
import com.correx.core.approvals.ApprovalProjector
@@ -10,7 +11,9 @@ 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.events.OrchestrationResumedEvent
import com.correx.core.events.events.StageCompletedEvent
import com.correx.core.events.events.StoredEvent
import com.correx.core.events.events.TransitionExecutedEvent
import com.correx.core.events.events.WorkflowCompletedEvent
import com.correx.core.events.events.WorkflowStartedEvent
import com.correx.core.events.orchestration.OrchestrationState
@@ -20,6 +23,7 @@ import com.correx.core.events.types.ArtifactId
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.events.types.TransitionId
import com.correx.core.kernel.orchestration.OrchestrationRepository
import com.correx.core.sessions.projections.replay.DefaultEventReplayer
import com.correx.core.tools.contract.Tool
@@ -139,6 +143,37 @@ class SessionEventBridgeTest {
assertEquals(ServerMessage.SnapshotComplete, sent[1])
}
@Test
fun `replaySnapshot populates recentEvents from event store`() = runTest {
val events = listOf(
storedEvent(WorkflowStartedEvent(sessionId, workflowId, stageId), seq = 1L),
storedEvent(TransitionExecutedEvent(sessionId, stageId, StageId("stage-2"), TransitionId("t1")), seq = 2L),
storedEvent(StageCompletedEvent(sessionId, stageId, TransitionId("t2")), seq = 3L),
storedEvent(WorkflowCompletedEvent(sessionId, stageId, 1), seq = 4L),
)
val store = fakeEventStore(allEventsList = events)
val orchRepo = activeOrchestrationRepository(status = OrchestrationStatus.COMPLETED)
val sent = mutableListOf<ServerMessage>()
val bridge = SessionEventBridge(
store,
noopArtifactStore,
orchRepo,
approvalRepository,
noopWorkflowRegistry,
noopToolRegistry,
) { sent.add(it) }
bridge.replaySnapshot()
assertEquals(2, sent.size)
val snapshot = sent[0] as ServerMessage.SessionSnapshot
// WorkflowStartedEvent is filtered out by eventToEntry (maps to null)
assertEquals(3, snapshot.recentEvents.size)
assertEquals(EventEntryDto(timestamp.toEpochMilliseconds(), "StageStarted", "stage-2"), snapshot.recentEvents[0])
assertEquals(EventEntryDto(timestamp.toEpochMilliseconds(), "StageCompleted", stageId.value), snapshot.recentEvents[1])
assertEquals(EventEntryDto(timestamp.toEpochMilliseconds(), "SessionCompleted", ""), snapshot.recentEvents[2])
}
@Test
fun `replaySnapshot skips unmapped events`() = runTest {
val events = listOf(
@@ -36,7 +36,7 @@ fun eventHistoryStripWidget(state: TuiState): Paragraph {
// Recent events (plain text, compact format) — capped to fit allocated height.
// Line 0 = stage info, lines 1-3 = events (max 3).
val events = selectedSession.recentEvents.take(3)
val events = selectedSession.recentEvents.takeLast(3)
if (events.isEmpty()) {
add(Line.from(Span.styled("no events", dimStyle)))
} else {
@@ -3,6 +3,7 @@ package com.correx.apps.tui.reducer
import com.correx.apps.server.protocol.ApprovalDecision
import com.correx.apps.server.protocol.ApprovalDto
import com.correx.apps.server.protocol.ClientMessage
import com.correx.apps.server.protocol.EventEntryDto
import com.correx.apps.server.protocol.PauseReason
import com.correx.apps.server.protocol.RiskSummaryDto
import com.correx.apps.server.protocol.ServerMessage
@@ -363,4 +364,29 @@ class SessionsReducerTest {
val (state, _) = reduce(action = Action.ServerEventReceived(msg))
assertEquals(null, state.sessions[0].pendingApproval)
}
@Test
fun `SessionSnapshot recentEvents are mapped to TuiEventEntry on summary`() {
val msg = ServerMessage.SessionSnapshot(
sessionId = SessionId("s1"),
workflowId = "wf-1",
state = SessionStateDto("COMPLETED", null, null),
pendingApprovals = emptyList(),
recentEvents = listOf(
EventEntryDto(timestamp = 1000L, type = "StageStarted", detail = "stage-1"),
EventEntryDto(timestamp = 2000L, type = "InferenceCompleted", detail = "stage-1"),
EventEntryDto(timestamp = 3000L, type = "SessionCompleted", detail = ""),
),
lastSequence = 1L,
lastSessionSequence = 1L,
)
val (state, _) = reduce(action = Action.ServerEventReceived(msg))
val events = state.sessions[0].recentEvents
assertEquals(3, events.size)
assertEquals("StageStarted", events[0].type)
assertEquals("stage-1", events[0].detail)
assertEquals("InferenceCompleted", events[1].type)
assertEquals("SessionCompleted", events[2].type)
assertEquals("", events[2].detail)
}
}