@sleep2agi/commhub-server 0.9.0-preview.8 → 0.9.0-preview.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +130 -10
- package/bin/commhub.ts +92 -11
- package/package.json +5 -5
- package/src/ack-create-request-transport.test.ts +159 -0
- package/src/ack-create-request.test.ts +5 -5
- package/src/admin-delete-network-http.test.ts +228 -0
- package/src/admin-networks-http.test.ts +88 -0
- package/src/agent-access-batch.test.ts +210 -0
- package/src/agent-access.ts +478 -0
- package/src/agent-acl-groups-http.test.ts +247 -0
- package/src/agent-acl-http.test.ts +520 -0
- package/src/agent-acl-slice3-http.test.ts +267 -0
- package/src/agent-groups-perf.test.ts +190 -0
- package/src/alias-filter.test.ts +72 -0
- package/src/alias-filter.ts +32 -0
- package/src/api-host-supervisors-can-create-nodes.test.ts +350 -0
- package/src/api-host-supervisors-fallback.test.ts +271 -0
- package/src/api-nodes-shape.test.ts +26 -10
- package/src/auth-sessions-http.test.ts +250 -0
- package/src/auth-validate.test.ts +5 -1
- package/src/auth.ts +450 -31
- package/src/avatar-validate.test.ts +163 -0
- package/src/avatar-validate.ts +103 -0
- package/src/blocked-exit-on-reply.test.ts +122 -0
- package/src/boot-side-effect.test.ts +124 -0
- package/src/commhub-bin-args.test.ts +85 -0
- package/src/config-apply-sec1.test.ts +107 -0
- package/src/config-apply-validate.test.ts +92 -0
- package/src/config-apply-validate.ts +81 -3
- package/src/create-node-blocked-gate.test.ts +356 -0
- package/src/create-node-tool-schema.test.ts +39 -0
- package/src/create-node-validate.test.ts +89 -1
- package/src/create-node-validate.ts +89 -7
- package/src/create-node-workdir.test.ts +255 -0
- package/src/create-node.ts +46 -1
- package/src/dashboard-slash-routing-http.test.ts +107 -0
- package/src/db-adapter-guard.test.ts +148 -0
- package/src/db-adapter-translate.test.ts +128 -0
- package/src/db-adapter.ts +323 -87
- package/src/db-timestamp.test.ts +38 -0
- package/src/db-timestamp.ts +15 -0
- package/src/db.ts +805 -33
- package/src/delivered-without-push.test.ts +140 -0
- package/src/desktop-message-seam.test.ts +150 -0
- package/src/desktop-message.test.ts +368 -0
- package/src/dm-file-scope-http.test.ts +178 -0
- package/src/ext-token-shared.test.ts +60 -0
- package/src/ext-validation-regression.test.ts +131 -0
- package/src/external-schedule-edits-http.test.ts +321 -0
- package/src/external-schedule-edits-timestamps.test.ts +64 -0
- package/src/external-schedule-edits.ts +327 -0
- package/src/file-download-authz-dev-open.test.ts +135 -0
- package/src/file-download-authz.test.ts +404 -0
- package/src/file-download-date-bucket.test.ts +436 -0
- package/src/file-network-scope-dev-open.test.ts +97 -0
- package/src/file-network-scope.test.ts +732 -0
- package/src/health-redaction.test.ts +642 -0
- package/src/http-gzip.test.ts +94 -0
- package/src/http-gzip.ts +110 -0
- package/src/hub-timestamp.test.ts +38 -0
- package/src/hub-timestamp.ts +27 -0
- package/src/human-dm.ts +215 -0
- package/src/inbox-count-sse.test.ts +310 -0
- package/src/inbox-count.ts +29 -0
- package/src/index.ts +15 -2331
- package/src/lifecycle-guard.ts +50 -0
- package/src/mcp-route-label-http.test.ts +120 -0
- package/src/mcp-write-network-resolution.test.ts +429 -0
- package/src/member-audit-network-id-http.test.ts +93 -0
- package/src/member-presence-http.test.ts +206 -0
- package/src/messages-ack-by-agent-http.test.ts +142 -0
- package/src/migrate-sqlite-to-pg.test.ts +47 -0
- package/src/migrate-sqlite-to-pg.ts +263 -0
- package/src/network-name-http.test.ts +75 -0
- package/src/network-scope.ts +224 -0
- package/src/node-attrs-validate.ts +88 -0
- package/src/node-attrs.test.ts +217 -0
- package/src/node-create-request-status-http.test.ts +92 -0
- package/src/node-files-transport.test.ts +344 -0
- package/src/node-id-alias.test.ts +188 -0
- package/src/node-lifecycle-controllable.test.ts +64 -0
- package/src/node-lifecycle-controllable.ts +51 -0
- package/src/node-logs-transport.test.ts +263 -0
- package/src/node-request-retention.test.ts +281 -0
- package/src/node-request-retention.ts +120 -0
- package/src/node-skills-transport.test.ts +209 -0
- package/src/node-token-privilege-http.test.ts +149 -0
- package/src/observer-avatar-http.test.ts +429 -0
- package/src/observer-push.test.ts +252 -0
- package/src/package-version-consistency.test.ts +25 -0
- package/src/peer-reply-atomic.test.ts +455 -0
- package/src/peer-reply-capability.test.ts +70 -0
- package/src/pg-worker.ts +87 -0
- package/src/push.ts +284 -7
- package/src/redact-tokens.test.ts +65 -0
- package/src/redact-tokens.ts +40 -0
- package/src/rename.ts +7 -2
- package/src/reply-target-derivation.test.ts +96 -0
- package/src/report-status-host-schema.test.ts +211 -0
- package/src/report-status-resume-id-handover.test.ts +150 -0
- package/src/require-explicit-test-db.ts +9 -0
- package/src/requirement-events-http.test.ts +257 -0
- package/src/requirement-events.test.ts +45 -0
- package/src/requirement-events.ts +143 -0
- package/src/requirement-notify.ts +134 -0
- package/src/requirement-participant-notify-http.test.ts +309 -0
- package/src/requirement-tag-ops-http.test.ts +266 -0
- package/src/requirement-tags.ts +89 -0
- package/src/requirements-completed-at-migration.test.ts +70 -0
- package/src/requirements-etag-http.test.ts +91 -0
- package/src/requirements-http.test.ts +709 -0
- package/src/requirements-list-slim-http.test.ts +315 -0
- package/src/requirements-mcp.test.ts +163 -0
- package/src/requirements-migrate.ts +225 -0
- package/src/requirements-people-http.test.ts +114 -0
- package/src/requirements-priority-lowest.test.ts +114 -0
- package/src/requirements-search-http.test.ts +182 -0
- package/src/requirements-search.test.ts +32 -0
- package/src/requirements-search.ts +138 -0
- package/src/requirements-seq-http.test.ts +193 -0
- package/src/requirements-seq-migration.test.ts +63 -0
- package/src/requirements-start-date.test.ts +125 -0
- package/src/requirements-stats-http.test.ts +288 -0
- package/src/requirements-stats.ts +164 -0
- package/src/requirements-upgrade-e2e.test.ts +126 -0
- package/src/requirements-upgrade-migration.test.ts +98 -0
- package/src/requirements.ts +1439 -0
- package/src/resolve-port.test.ts +57 -0
- package/src/resolve-port.ts +44 -0
- package/src/response-charset.test.ts +130 -0
- package/src/rest-explicit-columns-http.test.ts +216 -0
- package/src/rest-identity.test.ts +129 -0
- package/src/rest-identity.ts +100 -0
- package/src/rest-projections.ts +68 -0
- package/src/rest-write-network-resolution.test.ts +133 -0
- package/src/restricted-files.ts +73 -0
- package/src/retention.ts +15 -3
- package/src/route-callers-http.test.ts +120 -0
- package/src/route-timing.test.ts +211 -0
- package/src/route-timing.ts +258 -0
- package/src/rules-file-duplicate-alias.test.ts +234 -0
- package/src/rules-file-session-target.test.ts +207 -0
- package/src/rules-file-transport.test.ts +237 -0
- package/src/runtime-label.test.ts +22 -0
- package/src/runtime-label.ts +21 -0
- package/src/scheduled-doorbell-nodeless-session.test.ts +130 -0
- package/src/scheduled-reply-unread-http.test.ts +212 -0
- package/src/scheduled-run-terminal.test.ts +268 -0
- package/src/scheduled-skip-blocking-task.test.ts +120 -0
- package/src/scheduled-tasks-http.test.ts +638 -0
- package/src/scheduled-tasks.ts +597 -0
- package/src/send-dedup-request-id.test.ts +229 -0
- package/src/send-reply-agent-warning.test.ts +202 -0
- package/src/send-reply-alias-optional.test.ts +131 -0
- package/src/send-reply-attachments.test.ts +458 -0
- package/src/send_dedup.test.ts +54 -0
- package/src/send_dedup.ts +52 -6
- package/src/serve-error.test.ts +38 -0
- package/src/serve-error.ts +27 -0
- package/src/server.ts +4522 -0
- package/src/shared/external-schedule-contract.ts +60 -0
- package/src/side-thread-command-transport.test.ts +312 -0
- package/src/side-thread-command-transport.ts +318 -0
- package/src/side-thread-http-integration.test.ts +233 -0
- package/src/side-thread-http.ts +504 -0
- package/src/side-thread-production.test.ts +72 -0
- package/src/side-thread-production.ts +118 -0
- package/src/side-thread.test.ts +1067 -0
- package/src/side-thread.ts +2499 -0
- package/src/skillhub-http.test.ts +108 -0
- package/src/sqlite-close-checkpoint.test.ts +78 -0
- package/src/start-node.test.ts +109 -0
- package/src/status-alias-filter-http.test.ts +106 -0
- package/src/status-node-id-filter-http.test.ts +161 -0
- package/src/stop-delete-node.test.ts +1164 -0
- package/src/task-access-http.test.ts +379 -0
- package/src/task-access.ts +307 -0
- package/src/task-auth-origin-http.test.ts +91 -0
- package/src/task-auth-origin.test.ts +24 -0
- package/src/task-auth-origin.ts +7 -0
- package/src/task-consumption.test.ts +482 -0
- package/src/task-diagnostic-http.test.ts +149 -0
- package/src/task-diagnostic.test.ts +115 -0
- package/src/task-diagnostic.ts +118 -0
- package/src/task-idempotency-mcp.test.ts +142 -0
- package/src/task-idempotency.test.ts +60 -0
- package/src/task-idempotency.ts +75 -0
- package/src/task-lifecycle-watcher.test.ts +204 -0
- package/src/task-lifecycle-watcher.ts +49 -0
- package/src/task-network-resolution-http.test.ts +141 -0
- package/src/tasks-pagination-http.test.ts +141 -0
- package/src/tools-list-json-schema.test.ts +66 -0
- package/src/tools.ts +3534 -343
- package/src/update-provider.test.ts +65 -0
- package/src/upload-early-reject-guard.test.ts +155 -0
- package/src/uploads-http.test.ts +97 -15
- package/src/uploads.test.ts +66 -0
- package/src/uploads.ts +138 -5
- package/src/user-inbox-by-agent-http.test.ts +161 -0
- package/src/user-inbox-netid-notnull.test.ts +122 -0
- package/src/user-inbox-read-http.test.ts +223 -0
- package/src/user-inbox-schema.test.ts +54 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
// #461 — unit tests for the network observer SSE channel (push.ts).
|
|
2
|
+
//
|
|
3
|
+
// These tests read REAL bytes off the observer stream, so they are a
|
|
4
|
+
// live gate: stub pushNetworkObserverEvent out to a no-op and every
|
|
5
|
+
// "receives …" test below times out red. (Per 通信龙 2026-07-28 rule:
|
|
6
|
+
// a test that stays green when the core function is emptied is a fake
|
|
7
|
+
// gate.)
|
|
8
|
+
|
|
9
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
10
|
+
import {
|
|
11
|
+
__resetSSEClientsForTest,
|
|
12
|
+
createNetworkObserverStream,
|
|
13
|
+
createSSEStream,
|
|
14
|
+
createUserEventStream,
|
|
15
|
+
pushEvent,
|
|
16
|
+
pushNetworkObserverEvent,
|
|
17
|
+
pushUserEvent,
|
|
18
|
+
} from "./push";
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
__resetSSEClientsForTest();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
type Frame = Record<string, any>;
|
|
25
|
+
|
|
26
|
+
/** Read the next SSE data frame (skipping keepalive comments), with a
|
|
27
|
+
* hard timeout so a silent stream fails the test instead of hanging. */
|
|
28
|
+
async function readFrame(
|
|
29
|
+
reader: ReadableStreamDefaultReader<Uint8Array>,
|
|
30
|
+
timeoutMs = 2_000,
|
|
31
|
+
): Promise<Frame> {
|
|
32
|
+
const decoder = new TextDecoder();
|
|
33
|
+
let buf = "";
|
|
34
|
+
const deadline = Date.now() + timeoutMs;
|
|
35
|
+
while (Date.now() < deadline) {
|
|
36
|
+
const remaining = deadline - Date.now();
|
|
37
|
+
const result = await Promise.race([
|
|
38
|
+
reader.read(),
|
|
39
|
+
new Promise<never>((_, rej) =>
|
|
40
|
+
setTimeout(() => rej(new Error(`no SSE frame within ${timeoutMs}ms`)), remaining),
|
|
41
|
+
),
|
|
42
|
+
]);
|
|
43
|
+
if (result.done) throw new Error("stream ended before a data frame arrived");
|
|
44
|
+
buf += decoder.decode(result.value, { stream: true });
|
|
45
|
+
const sep = buf.indexOf("\n\n");
|
|
46
|
+
if (sep === -1) continue;
|
|
47
|
+
const rawFrame = buf.slice(0, sep);
|
|
48
|
+
buf = buf.slice(sep + 2);
|
|
49
|
+
const dataLine = rawFrame.split("\n").find((l) => l.startsWith("data: "));
|
|
50
|
+
if (!dataLine) continue; // keepalive comment frame — keep reading
|
|
51
|
+
return JSON.parse(dataLine.slice(6));
|
|
52
|
+
}
|
|
53
|
+
throw new Error(`no SSE frame within ${timeoutMs}ms`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
describe("#461 network observer stream", () => {
|
|
57
|
+
test("observer receives connected frame, then new_task summary with metadata only", async () => {
|
|
58
|
+
const res = createNetworkObserverStream("net_obs_a");
|
|
59
|
+
const reader = res.body!.getReader();
|
|
60
|
+
|
|
61
|
+
const connected = await readFrame(reader);
|
|
62
|
+
expect(connected.type).toBe("connected");
|
|
63
|
+
expect(connected.observer).toBe(true);
|
|
64
|
+
expect(connected.network_id).toBe("net_obs_a");
|
|
65
|
+
|
|
66
|
+
pushNetworkObserverEvent("net_obs_a", {
|
|
67
|
+
type: "new_task",
|
|
68
|
+
task_id: "task-123",
|
|
69
|
+
from: "sender-agent",
|
|
70
|
+
to: "receiver-agent",
|
|
71
|
+
status: "delivered",
|
|
72
|
+
priority: "high",
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const evt = await readFrame(reader);
|
|
76
|
+
expect(evt.type).toBe("new_task");
|
|
77
|
+
expect(evt.task_id).toBe("task-123");
|
|
78
|
+
expect(evt.from).toBe("sender-agent");
|
|
79
|
+
expect(evt.to).toBe("receiver-agent");
|
|
80
|
+
expect(evt.status).toBe("delivered");
|
|
81
|
+
expect(evt.priority).toBe("high");
|
|
82
|
+
expect(evt.network_id).toBe("net_obs_a");
|
|
83
|
+
expect(evt.scope).toBe("network");
|
|
84
|
+
// Summary contract: NO content field, ever.
|
|
85
|
+
expect("content" in evt).toBe(false);
|
|
86
|
+
expect("task" in evt).toBe(false);
|
|
87
|
+
await reader.cancel();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("observer receives new_reply summary", async () => {
|
|
91
|
+
const res = createNetworkObserverStream("net_obs_reply");
|
|
92
|
+
const reader = res.body!.getReader();
|
|
93
|
+
await readFrame(reader); // connected
|
|
94
|
+
|
|
95
|
+
pushNetworkObserverEvent("net_obs_reply", {
|
|
96
|
+
type: "new_reply",
|
|
97
|
+
task_id: "parent-task",
|
|
98
|
+
message_id: "msg-9",
|
|
99
|
+
from: "worker",
|
|
100
|
+
to: "boss",
|
|
101
|
+
status: "completed",
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const evt = await readFrame(reader);
|
|
105
|
+
expect(evt.type).toBe("new_reply");
|
|
106
|
+
expect(evt.task_id).toBe("parent-task");
|
|
107
|
+
expect(evt.message_id).toBe("msg-9");
|
|
108
|
+
expect(evt.status).toBe("completed");
|
|
109
|
+
expect("content" in evt).toBe(false);
|
|
110
|
+
expect("text" in evt).toBe(false);
|
|
111
|
+
await reader.cancel();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("events are network-isolated: observer on net B never sees net A traffic", async () => {
|
|
115
|
+
const resB = createNetworkObserverStream("net_obs_b");
|
|
116
|
+
const readerB = resB.body!.getReader();
|
|
117
|
+
await readFrame(readerB); // connected
|
|
118
|
+
|
|
119
|
+
// Push to A first, then a marker to B. Delivery per key is
|
|
120
|
+
// synchronous and ordered, so if B's next frame is the marker,
|
|
121
|
+
// the A event provably never reached B.
|
|
122
|
+
pushNetworkObserverEvent("net_obs_a2", { type: "new_task", task_id: "leaked" });
|
|
123
|
+
pushNetworkObserverEvent("net_obs_b", { type: "new_task", task_id: "marker-b" });
|
|
124
|
+
|
|
125
|
+
const evt = await readFrame(readerB);
|
|
126
|
+
expect(evt.task_id).toBe("marker-b");
|
|
127
|
+
await readerB.cancel();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("observer stream is isolated from session channels (both directions)", async () => {
|
|
131
|
+
const sessionRes = createSSEStream("agent-x", "net_obs_c");
|
|
132
|
+
const sessionReader = sessionRes.body!.getReader();
|
|
133
|
+
await readFrame(sessionReader); // connected
|
|
134
|
+
|
|
135
|
+
const obsRes = createNetworkObserverStream("net_obs_c");
|
|
136
|
+
const obsReader = obsRes.body!.getReader();
|
|
137
|
+
await readFrame(obsReader); // connected
|
|
138
|
+
|
|
139
|
+
// Session push must NOT hit the observer; observer push must NOT
|
|
140
|
+
// hit the session. Marker technique as above.
|
|
141
|
+
pushEvent("agent-x", { type: "new_task", inbox_count: 1, marker: "to-session" }, "net_obs_c");
|
|
142
|
+
pushNetworkObserverEvent("net_obs_c", { type: "new_task", task_id: "to-observer" });
|
|
143
|
+
|
|
144
|
+
const sessionEvt = await readFrame(sessionReader);
|
|
145
|
+
expect(sessionEvt.marker).toBe("to-session");
|
|
146
|
+
expect("task_id" in sessionEvt).toBe(false);
|
|
147
|
+
|
|
148
|
+
const obsEvt = await readFrame(obsReader);
|
|
149
|
+
expect(obsEvt.task_id).toBe("to-observer");
|
|
150
|
+
expect("marker" in obsEvt).toBe(false);
|
|
151
|
+
|
|
152
|
+
await sessionReader.cancel();
|
|
153
|
+
await obsReader.cancel();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("null / undefined networkId is a silent no-op", () => {
|
|
157
|
+
// Must not throw — legacy null-network traffic has no observer
|
|
158
|
+
// stream by design (documented in #461 / the PR).
|
|
159
|
+
expect(() => pushNetworkObserverEvent(null, { type: "new_task" })).not.toThrow();
|
|
160
|
+
expect(() => pushNetworkObserverEvent(undefined, { type: "new_task" })).not.toThrow();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("multiple observers on the same network all receive the event", async () => {
|
|
164
|
+
const r1 = createNetworkObserverStream("net_obs_multi");
|
|
165
|
+
const r2 = createNetworkObserverStream("net_obs_multi");
|
|
166
|
+
const reader1 = r1.body!.getReader();
|
|
167
|
+
const reader2 = r2.body!.getReader();
|
|
168
|
+
await readFrame(reader1);
|
|
169
|
+
await readFrame(reader2);
|
|
170
|
+
|
|
171
|
+
pushNetworkObserverEvent("net_obs_multi", { type: "new_task", task_id: "fanout" });
|
|
172
|
+
|
|
173
|
+
expect((await readFrame(reader1)).task_id).toBe("fanout");
|
|
174
|
+
expect((await readFrame(reader2)).task_id).toBe("fanout");
|
|
175
|
+
await reader1.cancel();
|
|
176
|
+
await reader2.cancel();
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe("Desktop user SSE stream", () => {
|
|
181
|
+
test("user stream receives connected frame, then desktop_message", async () => {
|
|
182
|
+
const res = createUserEventStream("net_user_a", "u_user_a");
|
|
183
|
+
const reader = res.body!.getReader();
|
|
184
|
+
|
|
185
|
+
const connected = await readFrame(reader);
|
|
186
|
+
expect(connected.type).toBe("connected");
|
|
187
|
+
expect(connected.user).toBe(true);
|
|
188
|
+
expect(connected.network_id).toBe("net_user_a");
|
|
189
|
+
expect(connected.user_id).toBe("u_user_a");
|
|
190
|
+
|
|
191
|
+
pushUserEvent("net_user_a", "u_user_a", {
|
|
192
|
+
type: "desktop_message",
|
|
193
|
+
message_id: "dm_one",
|
|
194
|
+
from: "agent-a",
|
|
195
|
+
message: "hello",
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const evt = await readFrame(reader);
|
|
199
|
+
expect(evt.type).toBe("desktop_message");
|
|
200
|
+
expect(evt.message_id).toBe("dm_one");
|
|
201
|
+
expect(evt.from).toBe("agent-a");
|
|
202
|
+
expect(evt.message).toBe("hello");
|
|
203
|
+
expect(evt.network_id).toBe("net_user_a");
|
|
204
|
+
expect(evt.user_id).toBe("u_user_a");
|
|
205
|
+
expect(evt.scope).toBe("user");
|
|
206
|
+
await reader.cancel();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("user stream is isolated from alias and observer key spaces", async () => {
|
|
210
|
+
const userRes = createUserEventStream("net_user_iso", "u_iso");
|
|
211
|
+
const userReader = userRes.body!.getReader();
|
|
212
|
+
await readFrame(userReader);
|
|
213
|
+
|
|
214
|
+
const sessionRes = createSSEStream("u_iso", "net_user_iso");
|
|
215
|
+
const sessionReader = sessionRes.body!.getReader();
|
|
216
|
+
await readFrame(sessionReader);
|
|
217
|
+
|
|
218
|
+
const observerRes = createNetworkObserverStream("net_user_iso");
|
|
219
|
+
const observerReader = observerRes.body!.getReader();
|
|
220
|
+
await readFrame(observerReader);
|
|
221
|
+
|
|
222
|
+
pushEvent("u_iso", { type: "alias_marker", marker: "alias" }, "net_user_iso");
|
|
223
|
+
pushNetworkObserverEvent("net_user_iso", { type: "observer_marker", marker: "observer" });
|
|
224
|
+
pushUserEvent("net_user_iso", "u_iso", { type: "desktop_message", marker: "user" });
|
|
225
|
+
|
|
226
|
+
expect((await readFrame(sessionReader)).marker).toBe("alias");
|
|
227
|
+
expect((await readFrame(observerReader)).marker).toBe("observer");
|
|
228
|
+
expect((await readFrame(userReader)).marker).toBe("user");
|
|
229
|
+
|
|
230
|
+
await userReader.cancel();
|
|
231
|
+
await sessionReader.cancel();
|
|
232
|
+
await observerReader.cancel();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("same user and network fan out to multiple live clients; cancelling one leaves the other live", async () => {
|
|
236
|
+
const r1 = createUserEventStream("net_user_multi", "u_multi");
|
|
237
|
+
const r2 = createUserEventStream("net_user_multi", "u_multi");
|
|
238
|
+
const reader1 = r1.body!.getReader();
|
|
239
|
+
const reader2 = r2.body!.getReader();
|
|
240
|
+
await readFrame(reader1);
|
|
241
|
+
await readFrame(reader2);
|
|
242
|
+
|
|
243
|
+
pushUserEvent("net_user_multi", "u_multi", { type: "desktop_message", message_id: "dm_fanout_1" });
|
|
244
|
+
expect((await readFrame(reader1)).message_id).toBe("dm_fanout_1");
|
|
245
|
+
expect((await readFrame(reader2)).message_id).toBe("dm_fanout_1");
|
|
246
|
+
|
|
247
|
+
await reader1.cancel();
|
|
248
|
+
pushUserEvent("net_user_multi", "u_multi", { type: "desktop_message", message_id: "dm_fanout_2" });
|
|
249
|
+
expect((await readFrame(reader2)).message_id).toBe("dm_fanout_2");
|
|
250
|
+
await reader2.cancel();
|
|
251
|
+
});
|
|
252
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
type PackageManifest = { version: string };
|
|
6
|
+
type PackageLock = {
|
|
7
|
+
version: string;
|
|
8
|
+
packages: { "": { version: string } };
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const readJson = <T>(name: string): T =>
|
|
12
|
+
JSON.parse(readFileSync(join(import.meta.dir, "..", name), "utf8")) as T;
|
|
13
|
+
|
|
14
|
+
describe("server package version consistency", () => {
|
|
15
|
+
const manifest = readJson<PackageManifest>("package.json");
|
|
16
|
+
const lock = readJson<PackageLock>("package-lock.json");
|
|
17
|
+
|
|
18
|
+
test("package-lock top-level version matches package.json", () => {
|
|
19
|
+
expect(lock.version).toBe(manifest.version);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("package-lock root package version matches package.json", () => {
|
|
23
|
+
expect(lock.packages[""].version).toBe(manifest.version);
|
|
24
|
+
});
|
|
25
|
+
});
|