feat: bootstrap commercial AI drama platform
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { dbGet, dbRun } from "../server/db.mjs";
|
||||
|
||||
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 headers(token, scope = localScope) {
|
||||
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: ${JSON.stringify(result.payload)}`);
|
||||
return result.payload.session.token;
|
||||
}
|
||||
|
||||
const taskTitle = `collaboration-smoke-${Date.now()}`;
|
||||
const shotId = dbGet("SELECT id FROM shots WHERE episode_id = 'episode-thunder-mouth-01' ORDER BY shot_number LIMIT 1")?.id;
|
||||
assert.ok(shotId, "a seeded shot is required for task-link smoke");
|
||||
let ownerToken = "";
|
||||
let reviewerToken = "";
|
||||
let taskId = "";
|
||||
let commentId = "";
|
||||
let linkId = "";
|
||||
try {
|
||||
const anonymous = await request("/api/project-activity");
|
||||
assert.equal(anonymous.response.status, 401, "anonymous activity 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" })
|
||||
});
|
||||
assert.equal(created.response.status, 201, JSON.stringify(created.payload));
|
||||
taskId = created.payload.task.id;
|
||||
|
||||
const detail = await request(`/api/tasks/${encodeURIComponent(taskId)}/detail`, { headers: headers(ownerToken) });
|
||||
assert.equal(detail.response.ok, true, JSON.stringify(detail.payload));
|
||||
assert.equal(detail.payload.comments.length, 0, "new task should have no comments");
|
||||
|
||||
const comment = await request(`/api/tasks/${encodeURIComponent(taskId)}/comments`, {
|
||||
method: "POST",
|
||||
headers: headers(ownerToken),
|
||||
body: JSON.stringify({ body: "请 @u-review 在审片中心确认实际末帧证据", mentionUserIds: ["u-review"] })
|
||||
});
|
||||
assert.equal(comment.response.status, 201, JSON.stringify(comment.payload));
|
||||
commentId = comment.payload.comment.id;
|
||||
assert.equal(comment.payload.comments.length, 1, "comment must be returned in task detail");
|
||||
assert.equal(comment.payload.comments[0].mentions[0].id, "u-review", "mention must be resolved to a valid project member");
|
||||
|
||||
const reviewerNotifications = await request("/api/notifications?limit=200", { headers: headers(reviewerToken) });
|
||||
assert.equal(reviewerNotifications.response.ok, true, JSON.stringify(reviewerNotifications.payload));
|
||||
assert.ok(reviewerNotifications.payload.notifications.some((item) => item.eventKey === "task.commented" && item.targetId === taskId), "task comment must notify assignee");
|
||||
|
||||
const linked = await request(`/api/tasks/${encodeURIComponent(taskId)}/links`, {
|
||||
method: "POST",
|
||||
headers: headers(ownerToken),
|
||||
body: JSON.stringify({ linkType: "shot", targetId: shotId })
|
||||
});
|
||||
assert.equal(linked.response.status, 201, JSON.stringify(linked.payload));
|
||||
linkId = linked.payload.link.id;
|
||||
assert.equal(linked.payload.links.length, 1, "shot link must be returned in task detail");
|
||||
assert.equal(linked.payload.links[0].targetId, shotId, "shot link target mismatch");
|
||||
|
||||
const activity = await request("/api/project-activity?limit=100", { headers: headers(ownerToken) });
|
||||
assert.equal(activity.response.ok, true, JSON.stringify(activity.payload));
|
||||
assert.ok(activity.payload.activities.some((item) => item.action === "project.task.comment.created" && item.targetId === commentId), "comment must appear in project activity");
|
||||
assert.ok(activity.payload.activities.some((item) => item.action === "project.task.link.created" && item.targetId === linkId), "link must appear in project activity");
|
||||
|
||||
const crossTenant = await request(`/api/tasks/${encodeURIComponent(taskId)}/detail`, { headers: headers(ownerToken, northstarScope) });
|
||||
assert.equal(crossTenant.response.status, 404, "cross-organization task detail must be isolated");
|
||||
|
||||
const removed = await request(`/api/tasks/${encodeURIComponent(taskId)}/links/${encodeURIComponent(linkId)}`, { method: "DELETE", headers: headers(ownerToken) });
|
||||
assert.equal(removed.response.ok, true, JSON.stringify(removed.payload));
|
||||
assert.equal(removed.payload.links.length, 0, "task link must be removable");
|
||||
console.log(`task collaboration smoke passed: task=${taskId}, comment=${commentId}, shot=${shotId}`);
|
||||
} finally {
|
||||
if (linkId) dbRun("DELETE FROM task_links WHERE id = ?", [linkId]);
|
||||
if (commentId) dbRun("DELETE FROM task_comments WHERE id = ?", [commentId]);
|
||||
if (taskId) dbRun("DELETE FROM project_tasks WHERE id = ?", [taskId]);
|
||||
if (taskId) dbRun("DELETE FROM user_notifications WHERE target_id = ?", [taskId]);
|
||||
if (taskId) dbRun("DELETE FROM audit_logs WHERE target_id = ? OR metadata_json LIKE ?", [taskId, `%${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(() => {});
|
||||
}
|
||||
Reference in New Issue
Block a user