feat: bootstrap commercial AI drama platform
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
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 headers(token, scope) {
|
||||
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 tokens = [];
|
||||
try {
|
||||
const anonymous = await request("/api/notifications");
|
||||
assert.equal(anonymous.response.status, 401, "anonymous notifications must be rejected");
|
||||
|
||||
const ownerToken = await login("producer@local.test");
|
||||
tokens.push(ownerToken);
|
||||
const ownerHeaders = headers(ownerToken, localScope);
|
||||
const preferences = await request("/api/notification-preferences", { headers: ownerHeaders });
|
||||
assert.equal(preferences.response.ok, true, JSON.stringify(preferences.payload));
|
||||
assert.equal(preferences.payload.preferences.length, 7, "notification preference catalog must be complete");
|
||||
assert.equal(preferences.payload.preferences.find((item) => item.category === "billing")?.enabled, true, "billing preference must default to enabled");
|
||||
const disabledPreferences = await request("/api/notification-preferences/billing", {
|
||||
method: "PATCH",
|
||||
headers: ownerHeaders,
|
||||
body: JSON.stringify({ enabled: false })
|
||||
});
|
||||
assert.equal(disabledPreferences.response.ok, true, JSON.stringify(disabledPreferences.payload));
|
||||
const preferenceMarker = `preference-${Date.now()}`;
|
||||
const preferenceEvent = await request("/api/system/notifications/test", {
|
||||
method: "POST",
|
||||
headers: ownerHeaders,
|
||||
body: JSON.stringify({ event: "quota.warning", payload: { targetId: preferenceMarker, detail: "通知偏好 smoke 事件" } })
|
||||
});
|
||||
assert.equal(preferenceEvent.response.ok, true, JSON.stringify(preferenceEvent.payload));
|
||||
const ownerAfterPreference = await request("/api/notifications?limit=80", { headers: ownerHeaders });
|
||||
assert.equal(ownerAfterPreference.response.ok, true, JSON.stringify(ownerAfterPreference.payload));
|
||||
assert.ok(!ownerAfterPreference.payload.notifications.some((item) => item.targetId === preferenceMarker), "disabled category must not create a notification for that user");
|
||||
const restoredPreferences = await request("/api/notification-preferences/billing", {
|
||||
method: "PATCH",
|
||||
headers: ownerHeaders,
|
||||
body: JSON.stringify({ enabled: true })
|
||||
});
|
||||
assert.equal(restoredPreferences.response.ok, true, JSON.stringify(restoredPreferences.payload));
|
||||
const marker = `smoke-${Date.now()}`;
|
||||
const event = await request("/api/system/notifications/test", {
|
||||
method: "POST",
|
||||
headers: ownerHeaders,
|
||||
body: JSON.stringify({ event: "system.changed", payload: { targetId: marker, detail: "通知 smoke 验证事件" } })
|
||||
});
|
||||
assert.equal(event.response.ok, true, `notification event failed: ${JSON.stringify(event.payload)}`);
|
||||
assert.ok(event.payload.userNotifications?.length >= 1, "event must create in-app notifications for eligible recipients");
|
||||
|
||||
const ownerInbox = await request("/api/notifications?limit=40", { headers: ownerHeaders });
|
||||
assert.equal(ownerInbox.response.ok, true, JSON.stringify(ownerInbox.payload));
|
||||
const created = ownerInbox.payload.notifications.find((item) => item.targetId === marker);
|
||||
assert.ok(created, "owner must see the notification in the current workspace");
|
||||
assert.ok(ownerInbox.payload.unreadCount >= 1, "new notification must increase unread count");
|
||||
|
||||
const writerToken = await login("writer@local.test");
|
||||
tokens.push(writerToken);
|
||||
const writerHeaders = headers(writerToken, localScope);
|
||||
const writerReadOwnerMessage = await request(`/api/notifications/${encodeURIComponent(created.id)}`, {
|
||||
method: "PATCH",
|
||||
headers: writerHeaders,
|
||||
body: JSON.stringify({ read: true })
|
||||
});
|
||||
assert.equal(writerReadOwnerMessage.response.status, 404, "a different user cannot mark another user's notification read");
|
||||
|
||||
const ownerRead = await request(`/api/notifications/${encodeURIComponent(created.id)}`, {
|
||||
method: "PATCH",
|
||||
headers: ownerHeaders,
|
||||
body: JSON.stringify({ read: true })
|
||||
});
|
||||
assert.equal(ownerRead.response.ok, true, JSON.stringify(ownerRead.payload));
|
||||
assert.ok(ownerRead.payload.notifications.some((item) => item.id === created.id && item.read), "single notification must become read");
|
||||
|
||||
const ownerReadAll = await request("/api/notifications/read-all", { method: "POST", headers: ownerHeaders, body: "{}" });
|
||||
assert.equal(ownerReadAll.response.ok, true, JSON.stringify(ownerReadAll.payload));
|
||||
assert.equal(ownerReadAll.payload.unreadCount, 0, "read-all must clear the current user's unread count");
|
||||
|
||||
const northstar = await request("/api/notifications?limit=40", { headers: headers(ownerToken, northstarScope) });
|
||||
assert.equal(northstar.response.ok, true, JSON.stringify(northstar.payload));
|
||||
assert.ok(northstar.payload.notifications.every((item) => item.organizationId === "org-northstar"), "cross-organization notifications must be isolated");
|
||||
assert.ok(!northstar.payload.notifications.some((item) => item.targetId === marker), "northstar must not see local notification IDs");
|
||||
const northstarPreferences = await request("/api/notification-preferences", { headers: headers(ownerToken, northstarScope) });
|
||||
assert.equal(northstarPreferences.response.ok, true, JSON.stringify(northstarPreferences.payload));
|
||||
assert.equal(northstarPreferences.payload.preferences.find((item) => item.category === "billing")?.enabled, true, "notification preferences must be organization scoped");
|
||||
|
||||
console.log(`notifications smoke passed: owner=${ownerInbox.payload.notifications.length}, preference-filtered=true, writer-cross-user=blocked, northstar=${northstar.payload.notifications.length}`);
|
||||
} finally {
|
||||
for (const token of tokens) {
|
||||
await request("/api/auth/logout", { method: "POST", headers: { authorization: `Bearer ${token}` } }).catch(() => {});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user