feat(sse): scope timeline events to affected audiences

This commit is contained in:
2026-03-30 00:40:24 +02:00
parent 819345acfa
commit fac8c1c3a5
8 changed files with 106 additions and 14 deletions
@@ -999,7 +999,7 @@ describe("allocation entry resolution router", () => {
expect(db.assignment.delete).toHaveBeenCalledWith({
where: { id: "assignment_explicit_1" },
});
expect(emitAllocationDeleted).toHaveBeenCalledWith("assignment_explicit_1", "project_1");
expect(emitAllocationDeleted).toHaveBeenCalledWith("assignment_explicit_1", "project_1", "resource_1");
});
it("updates an explicit demand row through allocation.update", async () => {
@@ -5,6 +5,7 @@ import {
eventBus,
flushPendingEvents,
permissionAudience,
resourceAudience,
type SseEvent,
userAudience,
} from "../sse/event-bus.js";
@@ -231,4 +232,37 @@ describe("event-bus debounce", () => {
unsubscribeFirst();
unsubscribeSecond();
});
it("delivers planning events to both staff and the affected resource audience", () => {
const managerReceived: SseEvent[] = [];
const resourceReceived: SseEvent[] = [];
const unsubscribeManager = eventBus.subscribe((event) => {
managerReceived.push(event);
}, {
audiences: [permissionAudience("manageAllocations")],
includeUnscoped: false,
});
const unsubscribeResource = eventBus.subscribe((event) => {
resourceReceived.push(event);
}, {
audiences: [resourceAudience("res_1")],
includeUnscoped: false,
});
eventBus.emit(
SSE_EVENT_TYPES.ALLOCATION_UPDATED,
{ id: "a1", resourceId: "res_1" },
[permissionAudience("manageAllocations"), resourceAudience("res_1")],
);
vi.advanceTimersByTime(50);
expect(managerReceived).toHaveLength(1);
expect(resourceReceived).toHaveLength(1);
expect(managerReceived[0]!.type).toBe(SSE_EVENT_TYPES.ALLOCATION_UPDATED);
expect(resourceReceived[0]!.payload).toEqual({ id: "a1", resourceId: "res_1" });
unsubscribeManager();
unsubscribeResource();
});
});