122 lines
6.5 KiB
JavaScript
122 lines
6.5 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 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(() => {});
|
|
}
|
|
}
|