feat(web): add tools approvals models and images
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { HarnessAttachmentAdapter } from "../src/assistant/attachment-adapter.js";
|
||||
|
||||
describe("HarnessAttachmentAdapter", () => {
|
||||
it("uploads immediately, emits no base64, and revokes its local preview", async () => {
|
||||
const createObjectURL = vi.spyOn(URL, "createObjectURL").mockReturnValue("blob:preview");
|
||||
const revokeObjectURL = vi.spyOn(URL, "revokeObjectURL").mockImplementation(() => undefined);
|
||||
const upload = vi.fn().mockResolvedValue({
|
||||
attachment: { attachmentId: "att_server", sessionId: "ses_1", name: "one.png", mediaType: "image/png", byteSize: 68, width: 1, height: 1, status: "ready" },
|
||||
});
|
||||
const remove = vi.fn().mockResolvedValue({ removed: true });
|
||||
const adapter = new HarnessAttachmentAdapter({ uploadAttachment: upload, deleteAttachment: remove });
|
||||
const file = new File([new Uint8Array([137, 80, 78, 71])], "one.png", { type: "image/png" });
|
||||
|
||||
const pending = await adapter.add({ file });
|
||||
expect(upload).toHaveBeenCalledWith(file);
|
||||
expect(adapter.previewUrl("att_server")).toBe("blob:preview");
|
||||
|
||||
const complete = await adapter.send(pending);
|
||||
expect(JSON.stringify(complete)).not.toContain("base64");
|
||||
expect(revokeObjectURL).toHaveBeenCalledWith("blob:preview");
|
||||
expect(createObjectURL).toHaveBeenCalledWith(file);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { ModelSettings } from "../src/components/settings/ModelSettings.js";
|
||||
|
||||
describe("ModelSettings", () => {
|
||||
it("saves through the server, clears the local key, and never uses browser storage", async () => {
|
||||
const user = userEvent.setup();
|
||||
const localSpy = vi.spyOn(Storage.prototype, "setItem");
|
||||
const save = vi.fn().mockResolvedValue({ configured: true, source: "session", keyHint: "••••cret" });
|
||||
render(<ModelSettings open onClose={vi.fn()} busy={false} onSave={save} onTest={vi.fn()} onClear={vi.fn()} />);
|
||||
|
||||
await user.type(screen.getByLabelText("API Key"), "sk-browser-secret");
|
||||
await user.type(screen.getByLabelText("模型 ID"), "gpt-test");
|
||||
await user.click(screen.getByRole("button", { name: "保存配置" }));
|
||||
|
||||
expect(save).toHaveBeenCalledWith(expect.objectContaining({ apiKey: "sk-browser-secret", modelId: "gpt-test" }));
|
||||
expect(screen.getByLabelText("API Key")).toHaveValue("");
|
||||
expect(screen.getByText("••••cret")).toBeVisible();
|
||||
expect(localSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -11,3 +11,5 @@ class TestResizeObserver implements ResizeObserver {
|
||||
}
|
||||
|
||||
globalThis.ResizeObserver = TestResizeObserver;
|
||||
URL.createObjectURL = () => "blob:test";
|
||||
URL.revokeObjectURL = () => undefined;
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
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 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("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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user