import assert from "node:assert/strict"; const api = process.env.AI_DRAMA_API_BASE || "http://127.0.0.1:8787"; const localScope = { "x-organization-id": "org-studio-lab", "x-workspace-id": "ws-local-aidrama", "x-project-id": "thunder-mouth" }; const northstarScope = { "x-organization-id": "org-northstar", "x-workspace-id": "ws-northstar-main", "x-project-id": "northstar-pilot" }; 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 expectOk(result, label) { assert.equal(result.response.ok, true, `${label}: ${result.response.status} ${result.payload.detail || result.payload.error || ""}`); return result.payload; } async function login(email) { const result = expectOk(await request("/api/auth/login", { method: "POST", body: JSON.stringify({ email, password: "Demo@123456" }) }), `${email} login`); assert.ok(result.session?.token, `${email} login must return a session token`); return result.session.token; } function scopedHeaders(token, scope) { return { authorization: `Bearer ${token}`, ...scope }; } function assertContract(payload, label) { assert.ok(Array.isArray(payload.items), `${label} must return items[]`); assert.ok(payload.summary && typeof payload.summary.total === "number", `${label} must return summary.total`); assert.equal(payload.summary.total, payload.items.length, `${label} summary total must match visible items`); for (const item of payload.items) { assert.ok(item.id && item.kind && item.title && item.targetTab, `${label} work item must have identity and navigation`); assert.ok(["high", "medium", "low"].includes(item.priority), `${label} work item priority must be normalized`); assert.ok(item.updatedAt, `${label} work item must expose updatedAt`); } } const tokens = []; try { const anonymous = await request("/api/work-items"); assert.ok([401, 429].includes(anonymous.response.status), `anonymous work-items access must be rejected: ${anonymous.response.status}`); const ownerToken = await login("producer@local.test"); tokens.push(ownerToken); const ownerPayload = expectOk(await request("/api/work-items", { headers: scopedHeaders(ownerToken, localScope) }), "owner local work items"); assertContract(ownerPayload, "owner local"); assert.equal(ownerPayload.scope.organizationId, localScope["x-organization-id"], "owner scope must expose the requested organization"); assert.equal(ownerPayload.scope.workspaceId, localScope["x-workspace-id"], "owner scope must expose the requested workspace"); assert.equal(ownerPayload.scope.projectId, localScope["x-project-id"], "owner scope must expose the requested project"); assert.ok(ownerPayload.items.some((item) => item.kind === "review"), "owner should see current-project review work items"); assert.ok(ownerPayload.items.some((item) => item.kind === "job"), "owner should see current-project generation work items"); assert.ok(!JSON.stringify(ownerPayload).includes("endpoint"), "work-items must not leak model endpoint data"); const limited = expectOk(await request("/api/work-items?limit=1", { headers: scopedHeaders(ownerToken, localScope) }), "limited work items"); assert.equal(limited.items.length, 1, "work-items limit must be enforced"); const writerToken = await login("writer@local.test"); tokens.push(writerToken); const writerPayload = expectOk(await request("/api/work-items", { headers: scopedHeaders(writerToken, localScope) }), "writer local work items"); assertContract(writerPayload, "writer local"); assert.ok(writerPayload.items.some((item) => item.kind === "job"), "writer should see production jobs it can operate"); assert.ok(writerPayload.items.every((item) => ["job", "asset", "review", "invitation"].includes(item.kind)), "writer must not see delivery, billing, audit, or model work items"); assert.ok(!JSON.stringify(writerPayload).includes("billing"), "writer payload must not include billing data"); const reviewerToken = await login("review@local.test"); tokens.push(reviewerToken); const reviewerPayload = expectOk(await request("/api/work-items", { headers: scopedHeaders(reviewerToken, localScope) }), "reviewer local work items"); assertContract(reviewerPayload, "reviewer local"); assert.ok(reviewerPayload.items.some((item) => item.kind === "review"), "reviewer should see QA work items"); assert.ok(reviewerPayload.items.every((item) => ["review", "asset", "delivery", "invitation"].includes(item.kind)), "reviewer must not see generation or model work items"); const northstarPayload = expectOk(await request("/api/work-items", { headers: scopedHeaders(ownerToken, northstarScope) }), "owner northstar work items"); assertContract(northstarPayload, "owner northstar"); assert.ok(northstarPayload.items.some((item) => item.metadata?.shotId === "shot-northstar-pilot-001" || item.metadata?.deliveryId), "northstar scope should expose northstar production records"); assert.ok(northstarPayload.items.every((item) => !String(item.metadata?.shotId || "").startsWith("shot-01")), "northstar scope must not include local project shots"); const localAgain = expectOk(await request("/api/work-items", { headers: scopedHeaders(ownerToken, localScope) }), "owner local work items after scope switch"); assert.ok(localAgain.items.every((item) => !String(item.metadata?.shotId || "").startsWith("shot-northstar")), "switching back must not retain northstar work items"); console.log(`work-items smoke passed: owner=${ownerPayload.items.length}, writer=${writerPayload.items.length}, reviewer=${reviewerPayload.items.length}, northstar=${northstarPayload.items.length}`); } finally { for (const token of tokens) { await request("/api/auth/logout", { method: "POST", headers: { authorization: `Bearer ${token}` } }).catch(() => {}); } }