fix(swads): make daily report generation resilient
This commit is contained in:
@@ -38,12 +38,12 @@ class FakeFactory implements AgentSessionFactory {
|
||||
async create(input: AgentSessionFactoryInput): Promise<AgentSessionPort> { this.input = input; return this.port; }
|
||||
}
|
||||
|
||||
async function setup(supportsImages = true) {
|
||||
async function setup(supportsImages = true, runTimeoutMs = 5_000) {
|
||||
const root = await mkdtemp(path.join(tmpdir(), "studio-session-")); const sessionsRoot = path.join(root, "sessions"); await mkdir(sessionsRoot);
|
||||
await mkdir(path.join(root, ".agents/skills"), { recursive: true }); await mkdir(path.join(root, ".pi/skills"), { recursive: true });
|
||||
const catalog = new SkillCatalog(root, path.join(root, "agent-dir")); await catalog.reload();
|
||||
const events = new InMemoryEventStore(); const attachments = new FileAttachmentStore(sessionsRoot); const models = new InMemoryModelConnectionStore({}); const factory = new FakeFactory(supportsImages);
|
||||
const registry = new InMemorySessionRegistry(sessionsRoot, events, attachments, models, catalog, factory, 500, 5_000);
|
||||
const registry = new InMemorySessionRegistry(sessionsRoot, events, attachments, models, catalog, factory, 500, runTimeoutMs);
|
||||
const session = await registry.create(); await registry.configureModel(session.sessionId, { providerId: "fake", api: "openai-responses", apiKey: "test-secret", modelId: "fake-model", supportsImages });
|
||||
return { root, sessionsRoot, events, attachments, models, catalog, factory, registry, session };
|
||||
}
|
||||
@@ -71,6 +71,15 @@ describe("session lifecycle", () => {
|
||||
it("cancels an active run and disposes on deletion", async () => {
|
||||
const context = await setup(); await context.registry.send(context.session.sessionId, { text: "hello", attachmentIds: [] }); await context.registry.cancel(context.session.sessionId, "user"); expect(context.factory.port.aborted).toBe(true); await vi.waitFor(() => expect(context.session.busy).toBe(false)); await context.registry.delete(context.session.sessionId); expect(context.factory.port.disposed).toBe(true); await expect(stat(context.session.cwd)).rejects.toThrow();
|
||||
});
|
||||
it("treats the run timeout as an inactivity timeout", async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
const context = await setup(true, 100); await context.registry.send(context.session.sessionId, { text: "hello", attachmentIds: [] });
|
||||
await vi.advanceTimersByTimeAsync(80); context.factory.port.emit({ type: "message_update", message: { role: "assistant" }, assistantMessageEvent: { type: "text_delta", delta: "still working" } });
|
||||
await vi.advanceTimersByTimeAsync(80); expect(context.session.busy).toBe(true);
|
||||
await vi.advanceTimersByTimeAsync(21); await Promise.resolve(); expect(context.factory.port.aborted).toBe(true); expect(context.events.listAfter(context.session.sessionId).events.at(-1)?.type).toBe("run.cancelled");
|
||||
} finally { vi.useRealTimers(); }
|
||||
});
|
||||
it("maps fake Pi deltas and tool events centrally", async () => {
|
||||
const context = await setup(); const { runId } = await context.registry.send(context.session.sessionId, { text: "hello", attachmentIds: [] }); context.factory.port.emit({ type: "message_update", message: { role: "assistant" }, assistantMessageEvent: { type: "text_delta", delta: "流式" } }); context.factory.input?.toolRequested("tool_1", "mock_ads_metrics", { accountId: "demo-account", days: 7 }); context.factory.port.emit({ type: "tool_execution_start", toolCallId: "tool_1", toolName: "mock_ads_metrics", args: {} }); context.factory.port.emit({ type: "tool_execution_end", toolCallId: "tool_1", toolName: "mock_ads_metrics", result: { ok: true }, isError: false }); context.factory.port.complete(); await vi.waitFor(() => expect(context.session.busy).toBe(false)); const runEvents = context.events.listAfter(context.session.sessionId).events.filter((event) => event.runId === runId); expect(runEvents.map((event) => event.type)).toContain("assistant.delta"); expect(runEvents.filter((event) => event.type === "tool.requested")).toHaveLength(1); expect(runEvents.map((event) => event.type)).toContain("tool.completed");
|
||||
});
|
||||
@@ -95,9 +104,9 @@ describe("session lifecycle", () => {
|
||||
const context = await setup(false); await context.registry.send(context.session.sessionId, { text: "report", attachmentIds: [] });
|
||||
const accountKey = "acct_0123456789abcdef01234567", reportDate = "2026-09-06", reportId = "12345678-1234-4234-8234-123456789abc"; const url = `/api/reports/${accountKey}/${reportDate}/${reportId}`;
|
||||
const artifact = { accountKey, reportDate, reportId, previewUrl: `${url}/png`, downloads: { png: `${url}/png`, html: `${url}/html`, json: `${url}/json` } };
|
||||
context.factory.port.emit({ type: "tool_execution_end", toolCallId: "render", toolName: "render_swads_daily_report", isError: false, result: { content: [{ type: "text", text: "/private/SHOULD_NOT_LEAK full report body" }], details: artifact } });
|
||||
context.factory.port.emit({ type: "tool_execution_end", toolCallId: "render", toolName: "render_swads_daily_report", isError: false, result: { content: [{ type: "text", text: "/private/SHOULD_NOT_LEAK full report body" }], details: { artifact, markdown: "## 可见日报摘要" } } });
|
||||
context.factory.port.emit({ type: "tool_execution_end", toolCallId: "whoami", toolName: "swads_cli", isError: true, result: { content: [{ type: "text", text: "SWADS_AUTH_FAILED" }] } });
|
||||
const events = context.events.listAfter(context.session.sessionId).events; const generated = events.find((e) => e.type === "report.generated"); expect(generated?.payload).toMatchObject({ assistantMessageId: context.session.currentRun!.assistantMessageId, artifact }); expect(JSON.stringify(events)).not.toContain("SHOULD_NOT_LEAK"); expect(events.find((e) => e.type === "tool.failed")?.payload).toMatchObject({ error: "SWADS_AUTH_FAILED" }); context.factory.port.complete();
|
||||
const events = context.events.listAfter(context.session.sessionId).events; const generated = events.find((e) => e.type === "report.generated"); expect(generated?.payload).toMatchObject({ assistantMessageId: context.session.currentRun!.assistantMessageId, artifact, markdown: "## 可见日报摘要" }); expect(JSON.stringify(events)).not.toContain("SHOULD_NOT_LEAK"); expect(events.find((e) => e.type === "tool.failed")?.payload).toMatchObject({ error: "SWADS_AUTH_FAILED" }); context.factory.port.complete();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user