Files
agent/apps/web/tests/tools-approval.test.tsx
T

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();
});
});