Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

sse.test.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

sse.test.tsBlame108 lines · 1 contributor
febd4f0Claude1/**
2 * Unit tests for src/lib/sse.ts — the in-process pub/sub broadcaster.
3 *
4 * These tests exercise the pure module-level state. Because the registry is
5 * a module-level `Map`, each test uses a unique topic name so cross-test
6 * leakage is impossible; we also explicitly unsubscribe everything we
7 * subscribe.
8 */
9
10import { describe, it, expect } from "bun:test";
11import {
12 publish,
13 subscribe,
14 topicSubscriberCount,
15 type SSEEvent,
16} from "../lib/sse";
17
18describe("sse broadcaster", () => {
19 it("publish with no subscribers is a no-op", () => {
20 // No throw, no side effect. topicSubscriberCount stays zero.
21 expect(() =>
22 publish("repo:no-subs", { data: { hello: "world" } })
23 ).not.toThrow();
24 expect(topicSubscriberCount("repo:no-subs")).toBe(0);
25 });
26
27 it("a subscriber receives events published to its topic", () => {
28 const received: SSEEvent[] = [];
29 const unsub = subscribe("repo:alpha", (e) => received.push(e));
30
31 expect(topicSubscriberCount("repo:alpha")).toBe(1);
32
33 publish("repo:alpha", { event: "push", data: { sha: "deadbeef" } });
34 publish("repo:alpha", { event: "star", data: { count: 7 }, id: "42" });
35
36 expect(received).toHaveLength(2);
37 expect(received[0]?.event).toBe("push");
38 expect((received[0]?.data as any).sha).toBe("deadbeef");
39 expect(received[1]?.id).toBe("42");
40
41 unsub();
42 expect(topicSubscriberCount("repo:alpha")).toBe(0);
43 });
44
45 it("multiple subscribers on the same topic all receive each event", () => {
46 const a: SSEEvent[] = [];
47 const b: SSEEvent[] = [];
48 const c: SSEEvent[] = [];
49 const unsubA = subscribe("pr:beta", (e) => a.push(e));
50 const unsubB = subscribe("pr:beta", (e) => b.push(e));
51 const unsubC = subscribe("pr:beta", (e) => c.push(e));
52
53 expect(topicSubscriberCount("pr:beta")).toBe(3);
54
55 publish("pr:beta", { event: "review", data: "submitted" });
56
57 expect(a).toHaveLength(1);
58 expect(b).toHaveLength(1);
59 expect(c).toHaveLength(1);
60 expect(a[0]?.data).toBe("submitted");
61
62 unsubA();
63 unsubB();
64 unsubC();
65 expect(topicSubscriberCount("pr:beta")).toBe(0);
66 });
67
68 it("unsubscribe stops delivery for that handler only", () => {
69 const keeper: SSEEvent[] = [];
70 const leaver: SSEEvent[] = [];
71 const unsubKeeper = subscribe("user:gamma", (e) => keeper.push(e));
72 const unsubLeaver = subscribe("user:gamma", (e) => leaver.push(e));
73
74 publish("user:gamma", { data: "first" });
75 expect(keeper).toHaveLength(1);
76 expect(leaver).toHaveLength(1);
77
78 unsubLeaver();
79 expect(topicSubscriberCount("user:gamma")).toBe(1);
80
81 publish("user:gamma", { data: "second" });
82 expect(keeper).toHaveLength(2);
83 expect(leaver).toHaveLength(1); // unchanged — leaver is gone
84
85 unsubKeeper();
86 expect(topicSubscriberCount("user:gamma")).toBe(0);
87
88 // Topic entry should be cleaned up after last unsubscribe.
89 publish("user:gamma", { data: "third" });
90 expect(keeper).toHaveLength(2);
91 });
92
93 it("a throwing handler does not prevent other handlers from receiving", () => {
94 const good: SSEEvent[] = [];
95 const unsubBad = subscribe("repo:delta", () => {
96 throw new Error("boom");
97 });
98 const unsubGood = subscribe("repo:delta", (e) => good.push(e));
99
100 expect(() =>
101 publish("repo:delta", { data: "payload" })
102 ).not.toThrow();
103 expect(good).toHaveLength(1);
104
105 unsubBad();
106 unsubGood();
107 });
108});