Files
prowler/ui/app/(prowler)/lighthouse/_lib/event-reducer.test.ts

149 lines
3.7 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
createInitialLighthouseV2StreamState,
reduceLighthouseV2Event,
} from "./event-reducer";
describe("event-reducer", () => {
it("should append message deltas", () => {
// Given
const state = createInitialLighthouseV2StreamState("task-1");
// When
const next = reduceLighthouseV2Event(state, {
type: "message.delta",
content: "Hello",
});
// Then
expect(next.assistantText).toBe("Hello");
expect(next.status).toBe("streaming");
});
it("should pair tool start and end events", () => {
// Given
const state = reduceLighthouseV2Event(
createInitialLighthouseV2StreamState("task-1"),
{
type: "tool_call.start",
toolCallId: "tool-1",
toolName: "search",
},
);
// When
const next = reduceLighthouseV2Event(state, {
type: "tool_call.end",
toolCallId: "tool-1",
outcome: "success",
});
// Then
expect(next.toolCalls).toEqual([
{
id: "tool-1",
name: "search",
status: "completed",
outcome: "success",
},
]);
});
it("should preserve the live display order of text and tool events", () => {
// Given
let state = createInitialLighthouseV2StreamState("task-1");
// When
state = reduceLighthouseV2Event(state, {
type: "message.delta",
content: "Voy a buscar los findings por severidad",
});
state = reduceLighthouseV2Event(state, {
type: "tool_call.start",
toolCallId: "tool-1",
toolName: "prowler_search_security_findings",
});
state = reduceLighthouseV2Event(state, {
type: "tool_call.end",
toolCallId: "tool-1",
outcome: "success",
});
state = reduceLighthouseV2Event(state, {
type: "message.delta",
content: "Ahora voy a buscar en los criticos",
});
// Then
expect(state.activityItems).toEqual([
{
id: "text-0",
type: "text",
text: "Voy a buscar los findings por severidad",
},
{
id: "tool-1",
type: "tool_call",
name: "prowler_search_security_findings",
status: "completed",
outcome: "success",
},
{
id: "text-2",
type: "text",
text: "Ahora voy a buscar en los criticos",
},
]);
});
it("should mark message end as completed", () => {
// Given
const state = createInitialLighthouseV2StreamState("task-1");
// When
const next = reduceLighthouseV2Event(state, {
type: "message.end",
messageId: "message-1",
});
// Then
expect(next.status).toBe("completed");
expect(next.messageId).toBe("message-1");
expect(next.activeTaskId).toBeNull();
});
it("should store terminal errors", () => {
// Given
const state = createInitialLighthouseV2StreamState("task-1");
// When
const next = reduceLighthouseV2Event(state, {
type: "error",
code: "llm_error",
detail: "Provider failed",
});
// Then
expect(next.status).toBe("error");
expect(next.error).toEqual({
code: "llm_error",
detail: "Provider failed",
});
expect(next.activeTaskId).toBeNull();
});
it("should clear the task gate on disconnect so retry can recover", () => {
// Given
const state = createInitialLighthouseV2StreamState("task-1");
// When
const next = reduceLighthouseV2Event(state, { type: "disconnect" });
// Then
expect(next.status).toBe("disconnected");
// activeTaskId must be cleared: leaving it set keeps canSend false and
// makes the Retry button a no-op after a dropped SSE connection.
expect(next.activeTaskId).toBeNull();
});
});