100 lines
5.9 KiB
JavaScript
100 lines
5.9 KiB
JavaScript
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.equal(anonymous.response.status, 401, "anonymous work-items access must be rejected");
|
|
|
|
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(() => {});
|
|
}
|
|
}
|