48 lines
3.6 KiB
TypeScript
48 lines
3.6 KiB
TypeScript
import { LEGACY_AGENT_TOOL_MAP } from "@/lib/ai/canvas-agent-runtime/legacy-tool-map";
|
|
import { expect, test } from "@playwright/test";
|
|
import { readFileSync, existsSync } from "node:fs";
|
|
import { PROJECT_AGENT_OPERATIONS, projectOperationAuthorized, projectOperationUrl, sanitizeProjectToolOutput } from "@/lib/ai/canvas-agent-runtime/project-operation-catalog";
|
|
import { shouldUseCanvasAgentRuntime } from "@/lib/ai/canvas-agent-runtime/routing";
|
|
import { getCanvasAgentAccessManifest } from "@/lib/ai/canvas-agent-runtime/capability-access";
|
|
|
|
test("business grant uses only fixed implemented route/method pairs and no management or secret endpoints", () => {
|
|
expect(new Set(PROJECT_AGENT_OPERATIONS.map(item=>item.id)).size).toBe(PROJECT_AGENT_OPERATIONS.length);
|
|
for (const op of PROJECT_AGENT_OPERATIONS) {
|
|
expect(op.path).not.toMatch(/\/admin\/|\/internal\/|\/jobs\/|\/auth\/|\/payment\/|access-keys|clawd-chat|agent-chat/);
|
|
const relative=op.path.slice('/api/'.length)+'/route.ts';
|
|
const path=['app/api/','app/(chat)/api/'].map(base=>base+relative).find(existsSync);
|
|
if (!path) { expect(op.id).toBe('smart-edit.get'); continue; }
|
|
expect(readFileSync(path,'utf8')).toMatch(new RegExp(`export\\s+(?:async\\s+)?function\\s+${op.method}\\b`));
|
|
}
|
|
});
|
|
test("video canvas and storyboard permissions are explicitly paused and never admitted through a forged mode", () => {
|
|
expect(getCanvasAgentAccessManifest().filter(item=>item.workspace==='video').every(item=>!item.authorized)).toBe(true);
|
|
expect(PROJECT_AGENT_OPERATIONS.filter(item=>item.group==='分镜任务').every(item=>!projectOperationAuthorized(item))).toBe(true);
|
|
expect(shouldUseCanvasAgentRuntime({ composerMode:'agent', workspaceVariant:'video', contextPayload:{ source:'canvas',projectId:'p' }})).toBe(false);
|
|
const route=readFileSync('app/api/canvas/agent-chat/route.ts','utf8');
|
|
expect(route).toContain('isZcodeWorkspaceAuthorized(ownedProject.workspaceType)');
|
|
});
|
|
test("operation URL substitution rejects path escapes and keeps query values encoded", () => {
|
|
const op=PROJECT_AGENT_OPERATIONS.find(item=>item.id==='flow-file.get')!;
|
|
expect(()=>projectOperationUrl(op,{id:'../../admin/keys'},{})).toThrow();
|
|
expect(()=>projectOperationUrl(op,{id:'a%2Fadmin'},{})).toThrow();
|
|
expect(projectOperationUrl(op,{id:'safe-id'},{keyword:'a&x=http://evil'})).toBe('/api/flow-board/files/safe-id?keyword=a%26x%3Dhttp%3A%2F%2Fevil');
|
|
});
|
|
test("nested model/storage secrets cannot be returned to the native model", () => {
|
|
expect(sanitizeProjectToolOutput({ data:[{ id:'m',apiKey:'secret', provider:{ SK:'hidden',AK:'hidden',secretAccessKey:'hidden',baseUrl:'https://safe' } }], headers:{ authorization:'secret' } })).toEqual({data:[{id:'m',provider:{baseUrl:'https://safe'}}]});
|
|
});
|
|
|
|
test("every legacy planner tool has a concrete native counterpart or explicit video pause", async () => {
|
|
const names=[...readFileSync('lib/ai/tools-registry.ts','utf8').matchAll(/name:\s*"([^"]+)"/g)].map(match=>match[1]);
|
|
expect(names).toHaveLength(26);
|
|
for (const name of names) expect(LEGACY_AGENT_TOOL_MAP[name],name).toBeTruthy();
|
|
expect(LEGACY_AGENT_TOOL_MAP.generateVideo).toBe('paused:video-authorization');
|
|
});
|
|
|
|
test("image workspace mode menus cannot bypass the paused video authorization", () => {
|
|
const manifest=getCanvasAgentAccessManifest();
|
|
for (const key of ["newVideoCanvas","workflowNodeLibrary","addVideoInput","chatModeVideoMenu","exportVideoCanvas"])
|
|
expect(manifest.find(item=>item.id===`canvas.ui.${key}`)).toMatchObject({workspace:'video',authorized:false});
|
|
expect(manifest.find(item=>item.id==='canvas.ui.saveCanvas')).toMatchObject({workspace:'canvas',authorized:true});
|
|
});
|