import assert from "node:assert/strict"; import { rm } from "node:fs/promises"; import { resolve } from "node:path"; import { dbRun, withTransaction } from "../server/db.mjs"; const api = process.env.AI_DRAMA_API_BASE || "http://127.0.0.1:8787"; const projectRoot = resolve(import.meta.dirname, ".."); const scope = { "x-organization-id": "org-studio-lab", "x-workspace-id": "ws-local-aidrama", "x-project-id": "thunder-mouth" }; async function request(path, options = {}) { const response = await fetch(`${api}${path}`, { ...options, headers: { "content-type": "application/json", ...(options.headers || {}) } }); const payload = await response.json().catch(() => ({})); return { response, payload }; } async function login(email = "producer@local.test") { const result = await request("/api/auth/login", { method: "POST", body: JSON.stringify({ email, password: "Demo@123456" }) }); assert.equal(result.response.ok, true, `模型路由 smoke 登录失败:${result.payload.detail || result.payload.error || ""}`); return { authorization: `Bearer ${result.payload.session.token}`, ...scope }; } const headers = await login(); const writerHeaders = await login("writer@local.test"); const createdJobIds = []; let workflowRunId = ""; async function preview(body) { const result = await request("/api/jobs/preview", { method: "POST", headers, body: JSON.stringify(body) }); assert.equal(result.response.ok, true, `任务预览失败:${result.payload.detail || result.payload.error || ""}`); return result.payload; } function cleanupGeneratedRecords() { withTransaction(() => { for (const jobId of createdJobIds) { dbRun("DELETE FROM job_dependencies WHERE job_id = ? OR depends_on_job_id = ?", [jobId, jobId]); dbRun("DELETE FROM job_attempts WHERE job_id = ?", [jobId]); dbRun("DELETE FROM usage_events WHERE metadata_json LIKE ?", [`%${jobId}%`]); dbRun("DELETE FROM audit_logs WHERE target_type = 'generation_job' AND target_id = ?", [jobId]); dbRun("DELETE FROM generation_jobs WHERE id = ?", [jobId]); } if (workflowRunId) { dbRun("DELETE FROM audit_logs WHERE target_type = 'workflow_run' AND target_id = ?", [workflowRunId]); dbRun("DELETE FROM workflow_runs WHERE id = ?", [workflowRunId]); } }); } try { const imageRouteResolution = await request("/api/platform/model-routes/resolve", { method: "POST", headers, body: JSON.stringify({ workflowKey: "ai-manhua-drama", operationKey: "image-keyframe", kind: "单画面关键帧", adapter: "comfyui-optional", localOnly: true }) }); assert.equal(imageRouteResolution.response.status, 200, `模型路由试算失败:${imageRouteResolution.payload.detail || imageRouteResolution.payload.error || ""}`); assert.equal(imageRouteResolution.payload.resolution.source, "routing-policy", "模型路由试算必须优先命中业务路由"); assert.equal(imageRouteResolution.payload.resolution.route.routeId, "route-image-keyframe", "关键帧试算没有命中单画面路由"); assert.equal(imageRouteResolution.payload.resolution.connector.id, "owned-image", "试算不应被前端连接器选择绕过"); assert.equal(imageRouteResolution.payload.resolution.guard.status, "blocked", "未探活的本地关键帧连接器应被准入预检阻断"); assert.ok(imageRouteResolution.payload.resolution.guard.reasons.some((reason) => reason.code === "connector_not_ready"), "试算结果必须说明连接器尚未就绪"); const mixedRouteResolution = await request("/api/platform/model-routes/resolve", { method: "POST", headers, body: JSON.stringify({ adapter: "newapi-audio-production", kind: "固定 TTS 配音", localOnly: true }) }); assert.equal(mixedRouteResolution.response.status, 200, "混合连接器试算接口失败"); assert.equal(mixedRouteResolution.payload.resolution.guard.status, "blocked", "local-only 预检必须阻断混合/外部连接器"); assert.ok(mixedRouteResolution.payload.resolution.guard.reasons.some((reason) => reason.code === "local_only_violation"), "试算结果必须说明 local-only 冲突"); const deniedResolution = await request("/api/platform/model-routes/resolve", { method: "POST", headers: writerHeaders, body: JSON.stringify({ workflowKey: "ai-manhua-drama", operationKey: "image-keyframe" }) }); assert.equal(deniedResolution.response.status, 403, "普通编剧不应访问模型路由试算器"); const imagePreview = await preview({ kind: "单画面关键帧", shotId: "shot-01", adapter: "comfyui-optional" }); assert.equal(imagePreview.resolution.source, "routing-policy", "关键帧任务必须由路由策略解析"); assert.equal(imagePreview.resolution.route.routeId, "route-image-keyframe", "关键帧没有命中单画面路由"); assert.equal(imagePreview.resolution.route.modelKey, "qwen-image-2d-lock", "关键帧没有解析到 Qwen 单画面模型"); assert.equal(imagePreview.resolution.adapter.id, "owned-image", "路由策略不应被前端连接器选择绕过"); assert.equal(imagePreview.preview.job.modelEntryId, "catalog-qwen-image-2d", "请求合同必须记录模型目录条目"); const auditionPreview = await preview({ kind: "单句 TTS 试听", shotId: "shot-01", adapter: "local-tts" }); assert.equal(auditionPreview.resolution.source, "legacy-adapter", "单句试听应保留本地试听连接器,不进入正式配音路由"); assert.equal(auditionPreview.resolution.adapter.id, "local-tts", "单句试听没有使用本地 TTS 连接器"); const formalTtsPreview = await preview({ kind: "固定 TTS 配音", shotId: "shot-01", adapter: "owned-model-platform" }); assert.equal(formalTtsPreview.resolution.route.routeId, "route-voice-tts", "固定配音没有命中声音路由"); assert.equal(formalTtsPreview.resolution.route.requiresApproval, true, "混合固定配音必须要求审批"); const blockedVoice = await request("/api/jobs", { method: "POST", headers, body: JSON.stringify({ kind: "固定 TTS 配音", shotId: "shot-01", adapter: "owned-model-platform", approveExternal: true }) }); assert.equal(blockedVoice.response.status, 422, "未授权角色声线必须阻断固定 TTS 任务"); assert.equal(blockedVoice.payload.error, "voice_reference_not_approved", "固定 TTS 的声音授权错误码不正确"); const createdImage = await request("/api/jobs", { method: "POST", headers, body: JSON.stringify({ kind: "单画面关键帧", shotId: "shot-01", adapter: "owned-model-platform" }) }); assert.equal(createdImage.response.status, 201, `关键帧任务创建失败:${createdImage.payload.detail || createdImage.payload.error || ""}`); createdJobIds.push(createdImage.payload.job.id); assert.equal(createdImage.payload.job.routing.routeId, "route-image-keyframe", "已创建任务没有保存路由证据"); assert.equal(createdImage.payload.job.request.routing.modelKey, "qwen-image-2d-lock", "请求合同没有保存实际模型 Key"); assert.equal(createdImage.payload.job.cost_policy, "local", "路由成本策略没有写入任务"); const workflow = await request("/api/workflows/templates/workflow-global-ai-manhua-drama-v1/instantiate", { method: "POST", headers, body: JSON.stringify({ mode: "queue", shotId: "shot-01", adapter: "comfyui-optional", approveExternal: true }) }); assert.equal(workflow.response.status, 201, `工作流入队失败:${workflow.payload.detail || workflow.payload.error || ""}`); workflowRunId = workflow.payload.run.id; createdJobIds.push(...workflow.payload.jobs); assert.equal(workflow.payload.run.status, "blocked", "存在未授权固定声线时,工作流必须保持阻塞"); assert.equal(workflow.payload.jobs.length, 2, "未授权固定 TTS 步骤不得创建任务,前序图像和视频任务仍应保留"); for (const jobId of workflow.payload.jobs) { const detail = await request(`/api/jobs/${encodeURIComponent(jobId)}`, { headers }); assert.equal(detail.response.ok, true, "工作流任务详情读取失败"); assert.ok(detail.payload.job.routing?.routeId, "工作流创建的任务必须记录模型路由"); } console.log("model routing smoke passed: route resolution, local audition, voice-rights gate, workflow queue"); } finally { cleanupGeneratedRecords(); for (const jobId of createdJobIds) { await rm(resolve(projectRoot, "storage", "jobs", jobId), { recursive: true, force: true }); } }