Files
agent-desktop/console/src/utils/scanError.test.ts
T
Pine 8e6b3abac3 feat(channels/market): 阶段 4 渠道/市场层合并上游 v2.2.0
- PluginManager: 采用上游 useMarketPlugins(300行: 分页append/loadingMore/autoLoadBlocked/竞态AbortController/highlightFilter) + MarketPluginList(无限滚动LoadMoreSentinel+PluginViewToggle卡片/列表切换); OfficialPluginList.module.less 保留三处 var(--color-border) 定制+追加上游移动端@media; index.module.less 三向并合; 本地 index.tsx 保留(operator 权限 gate)
- Market: MarketPanel(新 install: MarketInstallController 接口)/useMarketInstall/useMarketSearch/SkillIcon/ResultCard 采用上游
- Channels: ChannelDrawer/channelConfig/constants 上游版; 定制点保留(channelIcons console=var(--color-primary)、PendingApprovalsDrawer var(--color-success))
- api/types/chat.ts 采用上游(ChatGroup/ChatSource/ChatGroupKind/group_id); pages/Agent/Skills/index.tsx 适配 useMarketInstall
- utils: chatGroups.ts + 14 个上游缺失工具测试补齐; CronJobs 测试补齐
- 测试修复: design-mock 补 Tabs 桩(上游 PluginViewToggle 依赖); MarketPluginList.test 采用上游版; skill.test 品牌映射云超服; lazyWithRetry.test 适配本地 dev-server 回退
- 验证: tsc 0 错误、build 通过、阶段 4 相关 45 测试文件 427 用例全过
2026-09-04 13:24:47 +08:00

129 lines
4.2 KiB
TypeScript

/**
* Security-scan error handling: parses scan failures embedded in error
* messages and surfaces blocked-skill findings via modals. Defects here
* hide security verdicts from the user.
*/
import { describe, it, expect, vi, beforeEach } from "vitest";
const modalError = vi.fn();
const modalWarning = vi.fn();
vi.mock("@agentscope-ai/design", () => ({
Modal: {
error: (...args: unknown[]) => modalError(...args),
warning: (...args: unknown[]) => modalWarning(...args),
},
}));
import {
tryParseScanError,
handleScanError,
checkScanWarnings,
} from "./scanError";
const t = ((key: string) => key) as any;
beforeEach(() => {
modalError.mockClear();
modalWarning.mockClear();
});
describe("tryParseScanError", () => {
it("returns null for non-Error values", () => {
expect(tryParseScanError("plain string")).toBeNull();
expect(tryParseScanError(null)).toBeNull();
});
it("returns null when the message has no JSON payload", () => {
expect(tryParseScanError(new Error("network down"))).toBeNull();
});
it("returns null when the JSON is not a scan failure", () => {
const error = new Error('failed {"type": "other", "detail": "x"}');
expect(tryParseScanError(error)).toBeNull();
});
it("parses a security_scan_failed payload", () => {
const payload = {
type: "security_scan_failed",
findings: [{ title: "eval usage", file_path: "index.ts" }],
};
const error = new Error(`Install failed ${JSON.stringify(payload)}`);
expect(tryParseScanError(error)).toEqual(payload);
});
it("returns null when the embedded JSON is malformed", () => {
const error = new Error("failed {broken json");
expect(tryParseScanError(error)).toBeNull();
});
});
describe("handleScanError", () => {
it("shows the error modal and reports handled for a scan failure", () => {
const payload = { type: "security_scan_failed", findings: [] };
const error = new Error(`boom ${JSON.stringify(payload)}`);
expect(handleScanError(error, t)).toBe(true);
expect(modalError).toHaveBeenCalledTimes(1);
expect(modalError.mock.calls[0][0].title).toBe(
"security.skillScanner.scanError.title",
);
});
it("returns false and shows nothing for a non-scan error", () => {
expect(handleScanError(new Error("generic failure"), t)).toBe(false);
expect(modalError).not.toHaveBeenCalled();
});
});
describe("checkScanWarnings", () => {
const finding = { title: "dangerous call", file_path: "a.ts" };
const warnedAlert = {
skill_name: "my-skill",
action: "warned",
findings: [finding],
};
const noAlerts = () => Promise.resolve([]);
const defaultCfg = () => Promise.resolve({ whitelist: [] } as any);
it("does nothing when there are no alerts", async () => {
await checkScanWarnings("my-skill", noAlerts, defaultCfg, t);
expect(modalWarning).not.toHaveBeenCalled();
});
it("does nothing when the skill is whitelisted", async () => {
const alerts = () => Promise.resolve([warnedAlert] as any);
const cfg = () =>
Promise.resolve({ whitelist: [{ skill_name: "my-skill" }] } as any);
await checkScanWarnings("my-skill", alerts, cfg, t);
expect(modalWarning).not.toHaveBeenCalled();
});
it("shows a warning modal for the latest warned alert of the skill", async () => {
const alerts = () => Promise.resolve([warnedAlert] as any);
await checkScanWarnings("my-skill", alerts, defaultCfg, t);
expect(modalWarning).toHaveBeenCalledTimes(1);
expect(modalWarning.mock.calls[0][0].title).toBe(
"security.skillScanner.scanError.title",
);
});
it("ignores alerts for other skills or non-warned actions", async () => {
const alerts = () =>
Promise.resolve([
{ skill_name: "other-skill", action: "warned", findings: [] },
{ skill_name: "my-skill", action: "blocked", findings: [] },
] as any);
await checkScanWarnings("my-skill", alerts, defaultCfg, t);
expect(modalWarning).not.toHaveBeenCalled();
});
it("swallows fetch failures without throwing (best-effort)", async () => {
const failing = () => Promise.reject(new Error("backend down"));
await expect(
checkScanWarnings("my-skill", failing, defaultCfg, t),
).resolves.toBeUndefined();
expect(modalWarning).not.toHaveBeenCalled();
});
});