Files
AgentCompany/tests/unit/selective-ipc.test.ts
T

216 lines
7.9 KiB
TypeScript

import { describe, it, expect } from "vitest";
import type {
DesktopEvent,
TaskUpdatedEvent,
RunUpdatedEvent,
ApprovalUpdatedEvent,
AgentUpdatedEvent,
BudgetChangedEvent,
} from "@shared/types";
import {
classifySnapshotEvent,
} from "@renderer/lib/snapshot-events";
describe("selective IPC event types", () => {
it("TaskUpdatedEvent discriminated by type field", () => {
const event: TaskUpdatedEvent = {
type: "task-updated",
taskId: "t-1",
action: "created",
};
expect(event.type).toBe("task-updated");
expect(event.taskId).toBe("t-1");
expect(event.action).toBe("created");
});
it("RunUpdatedEvent discriminated by type field", () => {
const event: RunUpdatedEvent = {
type: "run-updated",
runId: "r-1",
action: "completed",
};
expect(event.type).toBe("run-updated");
expect(event.runId).toBe("r-1");
expect(event.action).toBe("completed");
});
it("ApprovalUpdatedEvent discriminated by type field", () => {
const event: ApprovalUpdatedEvent = {
type: "approval-updated",
approvalId: "a-1",
action: "decided",
};
expect(event.type).toBe("approval-updated");
expect(event.approvalId).toBe("a-1");
expect(event.action).toBe("decided");
});
it("AgentUpdatedEvent discriminated by type field", () => {
const event: AgentUpdatedEvent = {
type: "agent-updated",
agentId: "ag-1",
action: "updated",
};
expect(event.type).toBe("agent-updated");
expect(event.agentId).toBe("ag-1");
expect(event.action).toBe("updated");
});
it("BudgetChangedEvent discriminated by type field", () => {
const event: BudgetChangedEvent = {
type: "budget-changed",
companyId: "c-1",
};
expect(event.type).toBe("budget-changed");
expect(event.companyId).toBe("c-1");
});
it("all five entity event types are assignable to DesktopEvent union", () => {
const events: DesktopEvent[] = [
{ type: "task-updated", taskId: "t-1", action: "created" },
{ type: "run-updated", runId: "r-1", action: "status_changed" },
{ type: "approval-updated", approvalId: "a-1", action: "created" },
{ type: "agent-updated", agentId: "ag-1", action: "paused" },
{ type: "budget-changed", companyId: "c-1" },
];
expect(events).toHaveLength(5);
});
it("DesktopEvent union has all members used by snapshot classification", () => {
const exhaustiveCheck = (event: DesktopEvent): string => {
switch (event.type) {
case "run-log":
return "run-log";
case "domain-changed":
return "domain-changed";
case "shortcut":
return "shortcut";
case "run-status":
return "run-status";
case "heartbeat-status":
return "heartbeat-status";
case "agent-message":
return "agent-message";
case "automation-fired":
return "automation-fired";
case "new-message":
return "new-message";
case "notification-click":
return "notification-click";
case "task-updated":
return "task-updated";
case "run-updated":
return "run-updated";
case "approval-updated":
return "approval-updated";
case "agent-updated":
return "agent-updated";
case "budget-changed":
return "budget-changed";
case "thread-wait-state-updated":
return "thread-wait-state-updated";
case "recovery-needed":
return "recovery-needed";
case "oauth:status":
return "oauth:status";
case "oauth:auth-url":
return "oauth:auth-url";
case "oauth:progress":
return "oauth:progress";
case "oauth:prompt-request":
return "oauth:prompt-request";
case "oauth:manual-code-request":
return "oauth:manual-code-request";
case "credentials-changed":
return "credentials-changed";
case "agent-event":
return "agent-event";
default: {
const _exhaustive: never = event;
return _exhaustive;
}
}
};
expect(typeof exhaustiveCheck).toBe("function");
});
});
describe("classifySnapshotEvent", () => {
it("maps direct entity events to one immediate entity decision", () => {
expect(classifySnapshotEvent({ type: "task-updated", taskId: "t-1", action: "updated" })).toEqual({
kind: "entity",
entry: { type: "task-updated", entityId: "t-1", action: "updated" },
});
expect(classifySnapshotEvent({ type: "run-updated", runId: "r-1", action: "status_changed" })).toEqual({
kind: "entity",
entry: { type: "run-updated", entityId: "r-1", action: "status_changed" },
});
expect(classifySnapshotEvent({ type: "approval-updated", approvalId: "a-1", action: "decided" })).toEqual({
kind: "entity",
entry: { type: "approval-updated", entityId: "a-1", action: "decided" },
});
expect(classifySnapshotEvent({ type: "agent-updated", agentId: "ag-1", action: "paused" })).toEqual({
kind: "entity",
entry: { type: "agent-updated", entityId: "ag-1", action: "paused" },
});
expect(classifySnapshotEvent({ type: "budget-changed", companyId: "company-1" })).toEqual({
kind: "entity",
entry: { type: "budget-changed", entityId: "company-1", action: "changed" },
});
});
it("maps legacy progress events to their owning entities", () => {
expect(classifySnapshotEvent({ type: "run-status", runId: "r-1", status: "running", message: "Running" })).toEqual({
kind: "entity",
entry: { type: "run-updated", entityId: "r-1", action: "status_changed" },
});
expect(classifySnapshotEvent({ type: "heartbeat-status", agentId: "ag-1", enabled: true, nextWakeAt: null })).toEqual({
kind: "entity",
entry: { type: "agent-updated", entityId: "ag-1", action: "updated" },
});
expect(classifySnapshotEvent({ type: "agent-message", taskId: "t-1", comment: {} } as DesktopEvent)).toEqual({
kind: "entity",
entry: { type: "task-updated", entityId: "t-1", action: "updated" },
});
});
it("routes domain-wide events to full refresh", () => {
const events: DesktopEvent[] = [
{ type: "domain-changed" },
{ type: "new-message" } as DesktopEvent,
{ type: "thread-wait-state-updated", threadId: "th-1", companyId: "c-1", waitStateId: "w-1", action: "created" },
{ type: "credentials-changed", connectorId: "claude-pro" },
{ type: "agent-event", runId: "r-1", agentId: "ag-1", companyId: "c-1", taskId: "t-1", emittedAt: "2026-01-01", event: { type: "x" } },
{ type: "oauth:status", connectorId: "claude-pro", status: "active" },
{ type: "oauth:auth-url", connectorId: "claude-pro", url: "https://example.com" },
{ type: "oauth:progress", connectorId: "claude-pro", message: "Working" },
{ type: "oauth:prompt-request", connectorId: "claude-pro", requestId: "req-1", message: "Code" },
{ type: "oauth:manual-code-request", connectorId: "claude-pro", requestId: "req-1" },
];
for (const event of events) {
expect(classifySnapshotEvent(event)).toEqual({ kind: "full-refresh" });
}
});
it("ignores events with independent listeners", () => {
const events: DesktopEvent[] = [
{ type: "automation-fired", ruleId: "rule-1", ruleName: "Rule", trigger: "schedule", action: "run_task" },
{ type: "recovery-needed", interruptedRuns: [] },
{ type: "run-log", runId: "r-1", stream: "stdout", chunk: "chunk" },
{ type: "shortcut", action: "refresh" },
{ type: "notification-click", section: "tasks", entityId: "t-1" },
];
for (const event of events) {
expect(classifySnapshotEvent(event)).toEqual({ kind: "ignore" });
}
});
it("routes unknown runtime event shapes to full refresh", () => {
expect(classifySnapshotEvent({ type: "future-event" } as unknown as DesktopEvent)).toEqual({
kind: "full-refresh",
});
});
});