feat(platform): harden access scoping and delivery baseline
This commit is contained in:
@@ -4,7 +4,9 @@ import {
|
||||
cancelPendingEvents,
|
||||
eventBus,
|
||||
flushPendingEvents,
|
||||
permissionAudience,
|
||||
type SseEvent,
|
||||
userAudience,
|
||||
} from "../sse/event-bus.js";
|
||||
|
||||
// Mock Redis so the module loads without a real connection.
|
||||
@@ -153,4 +155,80 @@ describe("event-bus debounce", () => {
|
||||
// The timestamp should be from the first event (not later)
|
||||
expect(received[0]!.timestamp).toBe(before);
|
||||
});
|
||||
|
||||
it("delivers scoped events only to matching audiences", () => {
|
||||
const managerReceived: SseEvent[] = [];
|
||||
const userReceived: SseEvent[] = [];
|
||||
const unsubscribeManager = eventBus.subscribe((event) => {
|
||||
managerReceived.push(event);
|
||||
}, {
|
||||
audiences: [permissionAudience("manageAllocations")],
|
||||
includeUnscoped: false,
|
||||
});
|
||||
const unsubscribeUser = eventBus.subscribe((event) => {
|
||||
userReceived.push(event);
|
||||
}, {
|
||||
audiences: [userAudience("user_1")],
|
||||
includeUnscoped: false,
|
||||
});
|
||||
|
||||
eventBus.emit(
|
||||
SSE_EVENT_TYPES.ALLOCATION_CREATED,
|
||||
{ id: "a1" },
|
||||
[permissionAudience("manageAllocations")],
|
||||
);
|
||||
eventBus.emit(
|
||||
SSE_EVENT_TYPES.NOTIFICATION_CREATED,
|
||||
{ notificationId: "n1" },
|
||||
[userAudience("user_1")],
|
||||
);
|
||||
|
||||
vi.advanceTimersByTime(50);
|
||||
|
||||
expect(managerReceived).toHaveLength(1);
|
||||
expect(managerReceived[0]!.type).toBe(SSE_EVENT_TYPES.ALLOCATION_CREATED);
|
||||
expect(userReceived).toHaveLength(1);
|
||||
expect(userReceived[0]!.type).toBe(SSE_EVENT_TYPES.NOTIFICATION_CREATED);
|
||||
|
||||
unsubscribeManager();
|
||||
unsubscribeUser();
|
||||
});
|
||||
|
||||
it("does not batch events from different audiences together", () => {
|
||||
const firstUserReceived: SseEvent[] = [];
|
||||
const secondUserReceived: SseEvent[] = [];
|
||||
const unsubscribeFirst = eventBus.subscribe((event) => {
|
||||
firstUserReceived.push(event);
|
||||
}, {
|
||||
audiences: [userAudience("user_1")],
|
||||
includeUnscoped: false,
|
||||
});
|
||||
const unsubscribeSecond = eventBus.subscribe((event) => {
|
||||
secondUserReceived.push(event);
|
||||
}, {
|
||||
audiences: [userAudience("user_2")],
|
||||
includeUnscoped: false,
|
||||
});
|
||||
|
||||
eventBus.emit(
|
||||
SSE_EVENT_TYPES.NOTIFICATION_CREATED,
|
||||
{ notificationId: "n1" },
|
||||
[userAudience("user_1")],
|
||||
);
|
||||
eventBus.emit(
|
||||
SSE_EVENT_TYPES.NOTIFICATION_CREATED,
|
||||
{ notificationId: "n2" },
|
||||
[userAudience("user_2")],
|
||||
);
|
||||
|
||||
vi.advanceTimersByTime(50);
|
||||
|
||||
expect(firstUserReceived).toHaveLength(1);
|
||||
expect(firstUserReceived[0]!.payload).toEqual({ notificationId: "n1" });
|
||||
expect(secondUserReceived).toHaveLength(1);
|
||||
expect(secondUserReceived[0]!.payload).toEqual({ notificationId: "n2" });
|
||||
|
||||
unsubscribeFirst();
|
||||
unsubscribeSecond();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user