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(() => {}); } }