94 lines
5.7 KiB
TypeScript
94 lines
5.7 KiB
TypeScript
import { render, screen } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { ApprovalDialog } from "../src/components/approvals/ApprovalDialog.js";
|
|
import { AdsMetricsTool } from "../src/components/tools/AdsMetricsTool.js";
|
|
import { ToolFallback } from "../src/components/tools/ToolFallback.js";
|
|
import type { ClientApproval } from "../src/store/reducer.js";
|
|
|
|
const pendingApproval: ClientApproval = {
|
|
approvalId: "ap_1",
|
|
toolCallId: "tool_1",
|
|
toolName: "save_report_draft",
|
|
arguments: { title: "周报", body: "内容" },
|
|
riskLevel: "medium",
|
|
expiresAt: "2026-09-07T09:00:00.000Z",
|
|
status: "pending",
|
|
};
|
|
|
|
describe("tool and approval UI", () => {
|
|
it("shows registered representative frames but never external frame URLs or a fake final pass", () => {
|
|
render(<ToolFallback type="tool-call" toolName="prepare_drama_sources" toolCallId="tool_media" args={{}} argsText="" result={{ jobId: "job_known", title: "Test drama", state: "sources_ready", sources: [{ episodeNo: 1, duration: 72.3, frames: [
|
|
{ frameId: "frame_known", time: 10.5, url: "/api/sessions/ses_1/editor/job_known/frames/frame_known" },
|
|
{ frameId: "frame_bad", time: 22, url: "https://external.example/image.jpg" },
|
|
] }] }} status={{ type: "complete" }} addResult={vi.fn()} resume={vi.fn()} respondToApproval={vi.fn()} />);
|
|
expect(screen.getAllByRole("img")).toHaveLength(1);
|
|
expect(screen.getByRole("img")).toHaveAttribute("src", "/api/sessions/ses_1/editor/job_known/frames/frame_known");
|
|
expect(screen.getByText(/尚未完成剧情验证/)).toBeVisible();
|
|
expect(screen.getByText("job_known")).toBeVisible();
|
|
});
|
|
it.each([
|
|
["prepare_drama_sources", "允许下载所选分集并在本机抽帧吗?", "批准本次下载"],
|
|
["read_drama_frames", "允许将字幕和代表帧发送给当前模型吗?", "批准本次分析"],
|
|
["render_editor_preview", "允许生成 10 秒 REVIEW 预览吗?", "批准本次渲染"],
|
|
])("explains the media boundary for %s", (toolName, title, button) => {
|
|
render(<ApprovalDialog request={{ ...pendingApproval, toolName, arguments: { jobId: "job_authorized" } }} onApprove={vi.fn()} onDeny={vi.fn()} />);
|
|
expect(screen.getByRole("dialog", { name: title })).toBeVisible();
|
|
expect(screen.getByRole("button", { name: button })).toBeVisible();
|
|
expect(screen.getByText(/job_authorized/)).toBeVisible();
|
|
expect(screen.queryByText("report.md")).not.toBeInTheDocument();
|
|
});
|
|
it("identifies licensed CMS content reads and their model destination", () => {
|
|
render(<ApprovalDialog request={{ ...pendingApproval, toolName: "cms_drama_inventory", arguments: { dramaId: "fixture-drama" } }} onApprove={vi.fn()} onDeny={vi.fn()} />);
|
|
expect(screen.getByRole("dialog", { name: "允许读取 CMS 内容并交给模型吗?" })).toBeVisible();
|
|
expect(screen.getByText(/fixture-drama/)).toBeVisible();
|
|
expect(screen.queryByText("report.md")).not.toBeInTheDocument();
|
|
});
|
|
it("offers only Session report download links and exposes PDF generation failures", () => {
|
|
render(<ToolFallback type="tool-call" toolName="generate_minis_report" toolCallId="tool_report" args={{}} argsText="" result={{ pdfStatus: "failed", warning: "PDF 未生成", files: [{ format: "html", url: "/api/sessions/ses_1/reports/rpt_1/html" }, { format: "pdf", url: "https://untrusted.example/private" }] }} status={{ type: "complete" }} addResult={vi.fn()} resume={vi.fn()} respondToApproval={vi.fn()} />);
|
|
expect(screen.getByRole("link", { name: "下载 HTML" })).toHaveAttribute("href", "/api/sessions/ses_1/reports/rpt_1/html");
|
|
expect(screen.queryByRole("link", { name: "下载 PDF" })).not.toBeInTheDocument();
|
|
expect(screen.getByText("PDF 未生成")).toBeVisible();
|
|
});
|
|
|
|
it("shows actual analytics scope and destination rather than pretending the action only saves a file", () => {
|
|
render(<ApprovalDialog request={{ ...pendingApproval, toolName: "metrics_semantic_query", arguments: { date_from: "2026-09-08", date_to: "2026-09-08", external_account_id: "test-account", metrics: ["spend"] } }} onApprove={vi.fn()} onDeny={vi.fn()} />);
|
|
expect(screen.getByRole("dialog", { name: "允许查询真实经营数据吗?" })).toBeVisible();
|
|
expect(screen.getByText(/2026-09-08/)).toBeVisible();
|
|
expect(screen.getByText(/test-account/)).toBeVisible();
|
|
expect(screen.getByRole("button", { name: "批准本次查询" })).toBeVisible();
|
|
expect(screen.queryByText("report.md")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("disables both decisions after submitting approval", async () => {
|
|
const user = userEvent.setup();
|
|
let resolveApproval: (() => void) | undefined;
|
|
const approve = vi.fn(() => new Promise<void>((resolve) => { resolveApproval = resolve; }));
|
|
render(<ApprovalDialog request={pendingApproval} onApprove={approve} onDeny={vi.fn()} />);
|
|
|
|
await user.click(screen.getByRole("button", { name: "批准并保存" }));
|
|
|
|
expect(screen.getByRole("button", { name: "批准并保存" })).toBeDisabled();
|
|
expect(screen.getByRole("button", { name: "拒绝" })).toBeDisabled();
|
|
expect(approve).toHaveBeenCalledTimes(1);
|
|
resolveApproval?.();
|
|
});
|
|
|
|
it("renders safe ads metrics fields", () => {
|
|
render(<AdsMetricsTool
|
|
type="tool-call"
|
|
toolName="mock_ads_metrics"
|
|
toolCallId="tool_1"
|
|
args={{ account: "demo-account", days: 7 }}
|
|
argsText=""
|
|
result={{ spend: 1024.5, revenue: 3088.2, roas: 3.01 }}
|
|
status={{ type: "complete" }}
|
|
addResult={vi.fn()}
|
|
resume={vi.fn()}
|
|
respondToApproval={vi.fn()}
|
|
/>);
|
|
expect(screen.getByText("demo-account")).toBeVisible();
|
|
expect(screen.getByText("3.01")).toBeVisible();
|
|
});
|
|
});
|