import { PermissionKey, SSE_EVENT_TYPES, SystemRole } from "@capakraken/shared"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { cancelPendingEvents, eventBus, permissionAudience, resourceAudience, type SseEvent, userAudience, } from "../sse/event-bus.js"; import { deriveUserSseSubscription } from "../sse/subscription-policy.js"; vi.mock("ioredis", () => { const RedisMock = vi.fn().mockImplementation(() => ({ on: vi.fn(), subscribe: vi.fn().mockResolvedValue(undefined), publish: vi.fn().mockImplementation(() => { throw new Error("Redis unavailable (test)"); }), })); return { Redis: RedisMock }; }); describe("sse subscription policy", () => { let consoleWarnSpy: ReturnType; beforeEach(() => { vi.useFakeTimers(); consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => undefined); }); afterEach(() => { cancelPendingEvents(); consoleWarnSpy.mockRestore(); vi.useRealTimers(); }); it("derives canonical user, resource, and permission audiences server-side", () => { const subscription = deriveUserSseSubscription({ userId: "user_1", systemRole: SystemRole.USER, resourceId: "res_1", permissionOverrides: { granted: [PermissionKey.VIEW_PLANNING, PermissionKey.MANAGE_ALLOCATIONS], }, }); expect(subscription.includeUnscoped).toBe(false); expect(subscription.permissions).toEqual(new Set([ PermissionKey.VIEW_PLANNING, PermissionKey.MANAGE_ALLOCATIONS, ])); expect(subscription.audiences).toEqual([ permissionAudience(PermissionKey.MANAGE_ALLOCATIONS), permissionAudience(PermissionKey.VIEW_PLANNING), resourceAudience("res_1"), userAudience("user_1"), ]); }); it("does not deliver planning events to a standard user without the matching audience", () => { const received: SseEvent[] = []; const unsubscribe = eventBus.subscribe( (event) => { received.push(event); }, deriveUserSseSubscription({ userId: "user_1", systemRole: SystemRole.USER, resourceId: "res_self", }), ); eventBus.emit( SSE_EVENT_TYPES.ALLOCATION_UPDATED, { id: "allocation_1", resourceId: "res_other" }, [permissionAudience(PermissionKey.MANAGE_ALLOCATIONS), resourceAudience("res_other")], ); vi.advanceTimersByTime(50); expect(received).toHaveLength(0); unsubscribe(); }); it("does not leak personal notifications to a manager subscribed with elevated permissions", () => { const received: SseEvent[] = []; const unsubscribe = eventBus.subscribe( (event) => { received.push(event); }, deriveUserSseSubscription({ userId: "manager_1", systemRole: SystemRole.MANAGER, }), ); eventBus.emit( SSE_EVENT_TYPES.NOTIFICATION_CREATED, { notificationId: "notification_1", userId: "user_2" }, [userAudience("user_2")], ); vi.advanceTimersByTime(50); expect(received).toHaveLength(0); unsubscribe(); }); it("delivers a multi-audience planning event only to matching manager and affected resource subscribers", () => { const managerReceived: SseEvent[] = []; const affectedUserReceived: SseEvent[] = []; const unrelatedUserReceived: SseEvent[] = []; const unsubscribeManager = eventBus.subscribe( (event) => { managerReceived.push(event); }, deriveUserSseSubscription({ userId: "manager_1", systemRole: SystemRole.MANAGER, }), ); const unsubscribeAffectedUser = eventBus.subscribe( (event) => { affectedUserReceived.push(event); }, deriveUserSseSubscription({ userId: "user_1", systemRole: SystemRole.USER, resourceId: "res_1", }), ); const unsubscribeUnrelatedUser = eventBus.subscribe( (event) => { unrelatedUserReceived.push(event); }, deriveUserSseSubscription({ userId: "user_2", systemRole: SystemRole.USER, resourceId: "res_2", }), ); eventBus.emit( SSE_EVENT_TYPES.ALLOCATION_UPDATED, { id: "allocation_1", resourceId: "res_1" }, [permissionAudience(PermissionKey.MANAGE_ALLOCATIONS), resourceAudience("res_1")], ); vi.advanceTimersByTime(50); expect(managerReceived).toHaveLength(1); expect(affectedUserReceived).toHaveLength(1); expect(unrelatedUserReceived).toHaveLength(0); unsubscribeManager(); unsubscribeAffectedUser(); unsubscribeUnrelatedUser(); }); });