146 lines
4.7 KiB
TypeScript
146 lines
4.7 KiB
TypeScript
import { render, screen, within } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import type { HarnessEvent } from "@agent-studio/shared";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { App } from "../src/App.js";
|
|
import {
|
|
createInitialHarnessState,
|
|
reduceHarnessEvent,
|
|
type HarnessClientState,
|
|
} from "../src/store/reducer.js";
|
|
|
|
const base = {
|
|
sessionId: "ses_full_flow",
|
|
timestamp: "2026-09-08T08:00:00.000Z",
|
|
};
|
|
|
|
function event<T extends HarnessEvent>(value: Omit<T, "eventId" | "sessionId" | "timestamp">): T {
|
|
return {
|
|
...base,
|
|
...value,
|
|
eventId: `evt_${value.sequence}`,
|
|
} as T;
|
|
}
|
|
|
|
function fullFlowSnapshot(): HarnessClientState {
|
|
const events: HarnessEvent[] = [
|
|
event({
|
|
type: "user.message",
|
|
sequence: 1,
|
|
payload: { messageId: "msg_user", text: "分析最近 7 天素材并生成报告", attachmentIds: ["att_1"] },
|
|
}),
|
|
event({ type: "run.started", sequence: 2, runId: "run_1", payload: { modelId: "gpt-test" } }),
|
|
event({
|
|
type: "skill.requested",
|
|
sequence: 3,
|
|
runId: "run_1",
|
|
payload: { name: "ads-analysis", source: "project" },
|
|
}),
|
|
event({
|
|
type: "skill.loaded",
|
|
sequence: 4,
|
|
runId: "run_1",
|
|
payload: { name: "ads-analysis", source: "project" },
|
|
}),
|
|
event({
|
|
type: "assistant.delta",
|
|
sequence: 5,
|
|
runId: "run_1",
|
|
payload: { messageId: "msg_assistant", delta: "已读取分析方法。" },
|
|
}),
|
|
event({
|
|
type: "tool.requested",
|
|
sequence: 6,
|
|
runId: "run_1",
|
|
payload: {
|
|
toolCallId: "tool_metrics",
|
|
toolName: "mock_ads_metrics",
|
|
arguments: { accountId: "demo-account", days: 7 },
|
|
},
|
|
}),
|
|
event({
|
|
type: "tool.completed",
|
|
sequence: 7,
|
|
runId: "run_1",
|
|
payload: {
|
|
toolCallId: "tool_metrics",
|
|
toolName: "mock_ads_metrics",
|
|
result: { spend: 1024.5, revenue: 3088.2, roas: 3.01, conversions: 44 },
|
|
},
|
|
}),
|
|
event({
|
|
type: "tool.requested",
|
|
sequence: 8,
|
|
runId: "run_1",
|
|
payload: {
|
|
toolCallId: "tool_report",
|
|
toolName: "save_report_draft",
|
|
arguments: { title: "广告素材分析报告", body: "近 7 天投放表现与下一步建议。" },
|
|
},
|
|
}),
|
|
event({
|
|
type: "approval.required",
|
|
sequence: 9,
|
|
runId: "run_1",
|
|
payload: {
|
|
approvalId: "ap_full_flow",
|
|
toolCallId: "tool_report",
|
|
toolName: "save_report_draft",
|
|
arguments: { title: "广告素材分析报告", body: "近 7 天投放表现与下一步建议。" },
|
|
riskLevel: "medium",
|
|
expiresAt: "2099-09-08T08:05:00.000Z",
|
|
},
|
|
}),
|
|
];
|
|
|
|
const state = events.reduce(reduceHarnessEvent, createInitialHarnessState({
|
|
sessionId: base.sessionId,
|
|
skills: [{
|
|
name: "ads-analysis",
|
|
description: "分析广告素材表现",
|
|
source: "project",
|
|
filePath: ".agents/skills/ads-analysis/SKILL.md",
|
|
diagnostics: [],
|
|
}],
|
|
}));
|
|
return { ...state, connection: "open" };
|
|
}
|
|
|
|
describe("complete training flow", () => {
|
|
it("renders the message, skill, tools, timeline, and server-owned approval as one flow", async () => {
|
|
const user = userEvent.setup();
|
|
const approve = vi.fn().mockResolvedValue({ approvalId: "ap_full_flow", status: "approved" });
|
|
const snapshot = fullFlowSnapshot();
|
|
const store = {
|
|
getSnapshot: () => snapshot,
|
|
subscribe: () => () => undefined,
|
|
sendMessage: vi.fn(),
|
|
cancel: vi.fn(),
|
|
start: vi.fn().mockResolvedValue(undefined),
|
|
getModelConfig: vi.fn().mockResolvedValue({
|
|
modelConfig: { configured: true, source: "session", modelId: "gpt-test", supportsImages: true },
|
|
}),
|
|
approve,
|
|
deny: vi.fn(),
|
|
};
|
|
|
|
render(<App store={store} autoStart={false} modelConfigured />);
|
|
|
|
expect(screen.getByText("分析最近 7 天素材并生成报告")).toBeVisible();
|
|
expect(screen.getByText("已读取分析方法。")).toBeVisible();
|
|
expect(screen.getByText("分析广告素材表现")).toBeVisible();
|
|
|
|
const metrics = screen.getByRole("region", { name: "广告指标工具" });
|
|
expect(within(metrics).getByText("3.01")).toBeVisible();
|
|
const report = screen.getByRole("region", { name: "保存报告草稿工具" });
|
|
expect(within(report).getByText("等待审批")).toBeVisible();
|
|
|
|
expect(screen.getByRole("dialog", { name: "允许写入报告草稿吗?" })).toBeVisible();
|
|
expect(screen.getByText("approval.required")).toBeVisible();
|
|
|
|
await user.click(screen.getByRole("button", { name: "批准并保存" }));
|
|
expect(approve).toHaveBeenCalledTimes(1);
|
|
expect(approve).toHaveBeenCalledWith("ap_full_flow");
|
|
});
|
|
});
|