import assert from "node:assert/strict"; import { dbRun } from "../server/db.mjs"; const api = process.env.AI_DRAMA_API_BASE || "http://127.0.0.1:8787"; 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 }; } function headers(token) { return { authorization: `Bearer ${token}`, ...scope }; } async function login(email) { const result = await request("/api/auth/login", { method: "POST", body: JSON.stringify({ email, password: "Demo@123456" }) }); assert.equal(result.response.ok, true, `${email} login failed`); return result.payload.session.token; } const taskTitle = `smoke-task-${Date.now()}`; let ownerToken = ""; let reviewerToken = ""; let taskId = ""; try { const anonymous = await request("/api/tasks"); assert.equal(anonymous.response.status, 401, "anonymous task access must be rejected"); ownerToken = await login("producer@local.test"); reviewerToken = await login("review@local.test"); const created = await request("/api/tasks", { method: "POST", headers: headers(ownerToken), body: JSON.stringify({ title: taskTitle, description: "验证项目任务的负责人、状态、截止时间和审计链路", kind: "review", priority: "high", assigneeUserId: "u-review", targetTab: "qa", dueAt: "2026-08-30T23:59:59.000Z" }) }); assert.equal(created.response.status, 201, `task creation failed: ${JSON.stringify(created.payload)}`); assert.equal(created.payload.task.title, taskTitle, "created task title mismatch"); assert.equal(created.payload.task.assignee.id, "u-review", "task assignee not persisted"); assert.equal(created.payload.task.priority, "high", "task priority not persisted"); taskId = created.payload.task.id; const ownerList = await request("/api/tasks?status=all", { headers: headers(ownerToken) }); assert.equal(ownerList.response.ok, true, "owner task list failed"); assert.ok(ownerList.payload.tasks.some((task) => task.id === taskId), "owner should see created task"); assert.ok(ownerList.payload.summary.total >= 1, "task summary must include created task"); const reviewerList = await request("/api/tasks?assignedTo=me", { headers: headers(reviewerToken) }); assert.equal(reviewerList.response.ok, true, "reviewer assigned task list failed"); assert.ok(reviewerList.payload.tasks.some((task) => task.id === taskId), "assignee should see own task"); const reviewerNotifications = await request("/api/notifications?limit=200", { headers: headers(reviewerToken) }); assert.equal(reviewerNotifications.response.ok, true, "reviewer notification list failed"); assert.ok(reviewerNotifications.payload.notifications.some((notification) => notification.eventKey === "task.assigned" && notification.targetId === taskId), "task assignment must create an in-app notification"); const reviewerUpdate = await request(`/api/tasks/${encodeURIComponent(taskId)}`, { method: "PATCH", headers: headers(reviewerToken), body: JSON.stringify({ status: "in_progress" }) }); assert.equal(reviewerUpdate.response.ok, true, "assignee status update failed"); assert.equal(reviewerUpdate.payload.task.status, "in_progress", "assignee status was not saved"); const reviewerEscalation = await request(`/api/tasks/${encodeURIComponent(taskId)}`, { method: "PATCH", headers: headers(reviewerToken), body: JSON.stringify({ title: "越权修改标题" }) }); assert.equal(reviewerEscalation.response.status, 403, "assignee must not edit task metadata"); const ownerUpdate = await request(`/api/tasks/${encodeURIComponent(taskId)}`, { method: "PATCH", headers: headers(ownerToken), body: JSON.stringify({ status: "done", priority: "medium" }) }); assert.equal(ownerUpdate.response.ok, true, "manager task update failed"); assert.equal(ownerUpdate.payload.task.status, "done", "manager status update was not saved"); assert.ok(ownerUpdate.payload.task.completedAt, "completed task must have completedAt"); const northstar = await request("/api/tasks", { headers: { authorization: `Bearer ${ownerToken}`, "x-organization-id": "org-northstar", "x-workspace-id": "ws-northstar-main", "x-project-id": "northstar-pilot" } }); assert.equal(northstar.response.ok, true, "northstar task scope request failed"); assert.ok(northstar.payload.tasks.every((task) => task.id !== taskId), "cross-organization task must not leak"); console.log(`tasks smoke passed: ${taskId}`); } finally { if (taskId) dbRun("DELETE FROM project_tasks WHERE id = ?", [taskId]); if (taskId) { dbRun("DELETE FROM user_notifications WHERE target_id = ?", [taskId]); dbRun("DELETE FROM audit_logs WHERE target_type = 'project_task' AND target_id = ?", [taskId]); } if (ownerToken) await request("/api/auth/logout", { method: "POST", headers: { authorization: `Bearer ${ownerToken}` } }).catch(() => {}); if (reviewerToken) await request("/api/auth/logout", { method: "POST", headers: { authorization: `Bearer ${reviewerToken}` } }).catch(() => {}); }