Spaces:
Runtime error
Runtime error
| /** | |
| * tests/unit/ui/comboFlowModel.test.ts | |
| * | |
| * TDD for `comboFlowModel` — the pure reducer/model core of Tela B. | |
| * Run: node --import tsx/esm --test tests/unit/ui/comboFlowModel.test.ts | |
| */ | |
| import { describe, it } from "node:test"; | |
| import assert from "node:assert/strict"; | |
| import { | |
| classifyFailKind, | |
| reduceComboEvent, | |
| comboRunToFlow, | |
| type ComboRunModel, | |
| type ComboEventInput, | |
| } from "../../../src/app/(dashboard)/dashboard/combos/live/comboFlowModel.ts"; | |
| import { FLOW_EDGE_COLORS } from "../../../src/shared/components/flow/edgeStyles.ts"; | |
| // ── helpers ─────────────────────────────────────────────────────────────── | |
| function mkAttempt( | |
| targetIndex: number, | |
| strategy = "priority", | |
| provider = `prov${targetIndex}`, | |
| model = `model${targetIndex}` | |
| ): ComboEventInput { | |
| return { | |
| comboName: "test-combo", | |
| targetIndex, | |
| provider, | |
| model, | |
| type: "attempt", | |
| strategy, | |
| timestamp: 1000 + targetIndex, | |
| }; | |
| } | |
| function mkFailed( | |
| targetIndex: number, | |
| error: string, | |
| provider = `prov${targetIndex}`, | |
| model = `model${targetIndex}` | |
| ): ComboEventInput { | |
| return { | |
| comboName: "test-combo", | |
| targetIndex, | |
| provider, | |
| model, | |
| type: "failed", | |
| error, | |
| latencyMs: 100, | |
| timestamp: 2000 + targetIndex, | |
| }; | |
| } | |
| function mkSucceeded( | |
| targetIndex: number, | |
| provider = `prov${targetIndex}`, | |
| model = `model${targetIndex}` | |
| ): ComboEventInput { | |
| return { | |
| comboName: "test-combo", | |
| targetIndex, | |
| provider, | |
| model, | |
| type: "succeeded", | |
| latencyMs: 42, | |
| timestamp: 3000 + targetIndex, | |
| }; | |
| } | |
| // Full 6-event cascade: | |
| // attempt(0) → failed(0,"429 rate limited") → attempt(1) → failed(1,"circuit open") → attempt(2) → succeeded(2) | |
| function buildFullRun(): ComboRunModel { | |
| let run: ComboRunModel | null = null; | |
| run = reduceComboEvent(run, mkAttempt(0)); | |
| run = reduceComboEvent(run, mkFailed(0, "429 rate limited")); | |
| run = reduceComboEvent(run, mkAttempt(1)); | |
| run = reduceComboEvent(run, mkFailed(1, "circuit open")); | |
| run = reduceComboEvent(run, mkAttempt(2)); | |
| run = reduceComboEvent(run, mkSucceeded(2)); | |
| return run; | |
| } | |
| // ── classifyFailKind ────────────────────────────────────────────────────── | |
| describe("classifyFailKind", () => { | |
| it("returns undefined when error is absent", () => { | |
| assert.equal(classifyFailKind(undefined), undefined); | |
| assert.equal(classifyFailKind(""), undefined); | |
| }); | |
| it("returns 'rate-limit' for 429 messages", () => { | |
| assert.equal(classifyFailKind("429 Too Many Requests"), "rate-limit"); | |
| assert.equal(classifyFailKind("upstream rate limit exceeded"), "rate-limit"); | |
| assert.equal(classifyFailKind("RATE_LIMIT_EXCEEDED"), "rate-limit"); | |
| }); | |
| it("returns 'circuit-open' for circuit breaker messages", () => { | |
| assert.equal(classifyFailKind("circuit open"), "circuit-open"); | |
| assert.equal(classifyFailKind("Provider circuit breaker is open"), "circuit-open"); | |
| assert.equal(classifyFailKind("CIRCUIT_OPEN"), "circuit-open"); | |
| }); | |
| it("returns 'cooldown' for cooldown messages", () => { | |
| assert.equal(classifyFailKind("connection cooldown active"), "cooldown"); | |
| assert.equal(classifyFailKind("cooldown period"), "cooldown"); | |
| }); | |
| it("returns 'other' for unrecognized error strings", () => { | |
| assert.equal(classifyFailKind("Internal server error"), "other"); | |
| assert.equal(classifyFailKind("timeout"), "other"); | |
| assert.equal(classifyFailKind("some unknown failure"), "other"); | |
| }); | |
| it("circuit-open takes precedence over rate-limit when both match", () => { | |
| // edge: a message with both circuit and rate — circuit wins by regex ordering | |
| const result = classifyFailKind("circuit open after 429"); | |
| assert.equal(result, "circuit-open"); | |
| }); | |
| }); | |
| // ── reduceComboEvent ────────────────────────────────────────────────────── | |
| describe("reduceComboEvent — basic events", () => { | |
| it("creates a new run on first attempt event (null input)", () => { | |
| const run = reduceComboEvent(null, mkAttempt(0)); | |
| assert.equal(run.comboName, "test-combo"); | |
| assert.equal(run.strategy, "priority"); | |
| assert.equal(run.outcome, "running"); | |
| assert.ok(run.startedAt > 0); | |
| assert.equal(run.finishedAt, undefined); | |
| assert.equal(run.targets.length, 1); | |
| assert.equal(run.targets[0].targetIndex, 0); | |
| assert.equal(run.targets[0].provider, "prov0"); | |
| assert.equal(run.targets[0].model, "model0"); | |
| assert.equal(run.targets[0].state, "attempting"); | |
| }); | |
| it("marks target as failed and sets failKind on failed event", () => { | |
| let run = reduceComboEvent(null, mkAttempt(0)); | |
| run = reduceComboEvent(run, mkFailed(0, "429 rate limited")); | |
| assert.equal(run.targets[0].state, "failed"); | |
| assert.equal(run.targets[0].failKind, "rate-limit"); | |
| assert.equal(run.targets[0].error, "429 rate limited"); | |
| assert.equal(run.outcome, "running"); // not done yet | |
| }); | |
| it("marks target as succeeded and sets outcome + finishedAt on succeeded event", () => { | |
| let run = reduceComboEvent(null, mkAttempt(0)); | |
| run = reduceComboEvent(run, mkSucceeded(0)); | |
| assert.equal(run.targets[0].state, "succeeded"); | |
| assert.equal(run.targets[0].latencyMs, 42); | |
| assert.equal(run.outcome, "succeeded"); | |
| assert.ok(run.finishedAt != null); | |
| }); | |
| }); | |
| describe("reduceComboEvent — full cascade (3 targets)", () => { | |
| it("produces 3 targets ordered by targetIndex", () => { | |
| const run = buildFullRun(); | |
| assert.equal(run.targets.length, 3); | |
| assert.equal(run.targets[0].targetIndex, 0); | |
| assert.equal(run.targets[1].targetIndex, 1); | |
| assert.equal(run.targets[2].targetIndex, 2); | |
| }); | |
| it("states are [failed, failed, succeeded]", () => { | |
| const run = buildFullRun(); | |
| assert.equal(run.targets[0].state, "failed"); | |
| assert.equal(run.targets[1].state, "failed"); | |
| assert.equal(run.targets[2].state, "succeeded"); | |
| }); | |
| it("failKinds are [rate-limit, circuit-open, undefined]", () => { | |
| const run = buildFullRun(); | |
| assert.equal(run.targets[0].failKind, "rate-limit"); | |
| assert.equal(run.targets[1].failKind, "circuit-open"); | |
| assert.equal(run.targets[2].failKind, undefined); | |
| }); | |
| it("outcome is 'succeeded' and finishedAt is set", () => { | |
| const run = buildFullRun(); | |
| assert.equal(run.outcome, "succeeded"); | |
| assert.ok(run.finishedAt != null); | |
| }); | |
| it("strategy is set from the attempt payload", () => { | |
| const run = buildFullRun(); | |
| assert.equal(run.strategy, "priority"); | |
| }); | |
| }); | |
| describe("reduceComboEvent — ordering", () => { | |
| it("keeps targets sorted by targetIndex even if events arrive out of order", () => { | |
| // Unusual but defensive: attempt(2) then attempt(0) | |
| let run = reduceComboEvent(null, mkAttempt(2)); | |
| run = reduceComboEvent(run, mkAttempt(0)); | |
| assert.equal(run.targets[0].targetIndex, 0); | |
| assert.equal(run.targets[1].targetIndex, 2); | |
| }); | |
| it("idempotently applies a repeated attempt for the same target", () => { | |
| let run = reduceComboEvent(null, mkAttempt(0)); | |
| run = reduceComboEvent(run, mkAttempt(0)); // duplicate | |
| assert.equal(run.targets.length, 1); | |
| assert.equal(run.targets[0].state, "attempting"); | |
| }); | |
| }); | |
| describe("reduceComboEvent — comboName key", () => { | |
| it("ignores events for a different comboName when run already exists", () => { | |
| let run = reduceComboEvent(null, mkAttempt(0)); | |
| // Event for a different combo — should be ignored, run returned unchanged | |
| const alienEvent: ComboEventInput = { | |
| comboName: "other-combo", | |
| targetIndex: 99, | |
| provider: "alien", | |
| model: "alien", | |
| type: "failed", | |
| error: "some error", | |
| timestamp: 9999, | |
| }; | |
| run = reduceComboEvent(run, alienEvent); | |
| assert.equal(run.targets.length, 1); | |
| assert.equal(run.comboName, "test-combo"); | |
| }); | |
| }); | |
| // ── comboRunToFlow ──────────────────────────────────────────────────────── | |
| describe("comboRunToFlow", () => { | |
| it("returns N+3 nodes for N targets (request + strategy + N targets + response)", () => { | |
| const run = buildFullRun(); | |
| const { nodes } = comboRunToFlow(run); | |
| // 3 targets → 1 request + 1 strategy + 3 targets + 1 response = 6 nodes | |
| assert.equal(nodes.length, 6, `expected 6 nodes, got ${nodes.length}`); | |
| }); | |
| it("node types: first=request, second=strategy, N middle=target, last=response", () => { | |
| const run = buildFullRun(); | |
| const { nodes } = comboRunToFlow(run); | |
| assert.equal(nodes[0].type, "request"); | |
| assert.equal(nodes[1].type, "strategy"); | |
| assert.equal(nodes[2].type, "target"); | |
| assert.equal(nodes[3].type, "target"); | |
| assert.equal(nodes[4].type, "target"); | |
| assert.equal(nodes[5].type, "response"); | |
| }); | |
| it("target nodes carry provider, model, state, failKind in data", () => { | |
| const run = buildFullRun(); | |
| const { nodes } = comboRunToFlow(run); | |
| const t0 = nodes[2]; | |
| assert.equal((t0.data as Record<string, unknown>).provider, "prov0"); | |
| assert.equal((t0.data as Record<string, unknown>).model, "model0"); | |
| assert.equal((t0.data as Record<string, unknown>).state, "failed"); | |
| assert.equal((t0.data as Record<string, unknown>).failKind, "rate-limit"); | |
| const t2 = nodes[4]; | |
| assert.equal((t2.data as Record<string, unknown>).state, "succeeded"); | |
| assert.equal((t2.data as Record<string, unknown>).failKind, undefined); | |
| }); | |
| it("strategy node carries the strategy name in data", () => { | |
| const run = buildFullRun(); | |
| const { nodes } = comboRunToFlow(run); | |
| assert.equal((nodes[1].data as Record<string, unknown>).strategy, "priority"); | |
| assert.equal((nodes[1].data as Record<string, unknown>).targetCount, 3); | |
| }); | |
| it("edges: N+3 sequential edges (one per node-to-node link)", () => { | |
| const run = buildFullRun(); | |
| const { edges } = comboRunToFlow(run); | |
| // 6 nodes → 5 edges: request→strategy, strategy→t0, t0→t1, t1→t2, t2→response | |
| assert.equal(edges.length, 5, `expected 5 edges, got ${edges.length}`); | |
| }); | |
| it("each edge connects consecutive nodes in order", () => { | |
| const run = buildFullRun(); | |
| const { nodes, edges } = comboRunToFlow(run); | |
| for (let i = 0; i < edges.length; i++) { | |
| assert.equal(edges[i].source, nodes[i].id, `edge[${i}].source mismatch`); | |
| assert.equal(edges[i].target, nodes[i + 1].id, `edge[${i}].target mismatch`); | |
| } | |
| }); | |
| it("failed edges are styled with error color", () => { | |
| const run = buildFullRun(); | |
| const { edges } = comboRunToFlow(run); | |
| // edge[2] is strategy→t0 (failed)… actually edges are: [0]req→strat, [1]strat→t0, [2]t0→t1, [3]t1→t2, [4]t2→resp | |
| // edge for t0 (failed): index 1 (strategy→t0) | |
| // The edge going INTO t0 should reflect t0 state | |
| const t0IncomingEdge = edges[1]; // strategy→target0 | |
| const style = t0IncomingEdge.style as Record<string, unknown> | undefined; | |
| assert.ok(style != null, "edge should have style"); | |
| assert.equal(style.stroke, FLOW_EDGE_COLORS.error, "failed target edge should be error color"); | |
| }); | |
| it("succeeded edge is styled with active/green color", () => { | |
| const run = buildFullRun(); | |
| const { edges } = comboRunToFlow(run); | |
| // edge[4] is t2→response (t2 succeeded) | |
| const t2ResponseEdge = edges[4]; | |
| const style = t2ResponseEdge.style as Record<string, unknown> | undefined; | |
| assert.ok(style != null); | |
| assert.equal( | |
| style.stroke, | |
| FLOW_EDGE_COLORS.active, | |
| "succeeded target edge should be active/green color" | |
| ); | |
| }); | |
| it("idle/attempting edges are styled with idle or last-used color (not error/green)", () => { | |
| // A run with one target still attempting | |
| let run = reduceComboEvent(null, mkAttempt(0)); | |
| const { edges } = comboRunToFlow(run); | |
| // edge[1] = strategy→t0 (attempting) | |
| const attemptingEdge = edges[1]; | |
| const style = attemptingEdge.style as Record<string, unknown> | undefined; | |
| assert.ok(style != null); | |
| assert.notEqual(style.stroke, FLOW_EDGE_COLORS.error); | |
| assert.notEqual(style.stroke, FLOW_EDGE_COLORS.active); | |
| }); | |
| it("produces deterministic node IDs", () => { | |
| const run = buildFullRun(); | |
| const { nodes } = comboRunToFlow(run); | |
| assert.equal(nodes[0].id, "request"); | |
| assert.equal(nodes[1].id, "strategy"); | |
| assert.equal(nodes[2].id, "target-0"); | |
| assert.equal(nodes[3].id, "target-1"); | |
| assert.equal(nodes[4].id, "target-2"); | |
| assert.equal(nodes[5].id, "response"); | |
| }); | |
| }); | |