agent-comms 2.21.17 → 2.21.18
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +1 -1
- package/dist/test/delivery-engine-delivery.test.d.ts +2 -0
- package/dist/test/delivery-engine-delivery.test.d.ts.map +1 -0
- package/dist/test/delivery-engine-delivery.test.js +740 -0
- package/dist/test/delivery-engine-delivery.test.js.map +1 -0
- package/dist/test/delivery-engine.test.d.ts +2 -0
- package/dist/test/delivery-engine.test.d.ts.map +1 -0
- package/dist/test/delivery-engine.test.js +690 -0
- package/dist/test/delivery-engine.test.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,690 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Direct, DI-based unit tests for DeliveryEngine -- the busiest collaborator in mesh-store.ts's own split (see its file header), previously exercised only indirectly through end-to-end room-send/state-sync integration tests, leaving many individual branches (version-gate boundaries, subscribedRooms union-vs-replace, dedup caps, timer-scheduled auto-mark-read, replay's per-type fire rules) unobserved. DeliveryEngineDeps is a narrow, injectable surface built exactly for this: every Map/Set is a real instance so effects are asserted by inspecting them directly, and every collaborator boundary (transport, sendRoomRequestToMember, onDelivery/onPatch callbacks, isShutDown) is a vi.fn() this file controls per test. loadRoomTokens is a free function reading a real identity file, not part of the injectable deps -- mocked here since markRead only ever forwards its return value opaquely, never inspects or verifies it. Split from delivery-engine-delivery.test.ts to satisfy the repo's max-lines cap: this file covers presence/queueing/membership-merge/state-sync/patch-application plus the top-level deliver/drainDelivery entry points (the latter two moved here from delivery-engine-delivery.test.ts to rebalance both files under the cap after a later round of survivor-closing tests grew it past 800 lines); delivery-engine-delivery.test.ts covers broadcast/fireLocalDelivery/notification/markRead. Both files share an identical preamble (helpers, fakes, makeHarness) by necessity of the split.
|
|
3
|
+
*/
|
|
4
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { loadRoomTokens } from "../core/identity-store.js";
|
|
6
|
+
import { randomId } from "../core/random-id.js";
|
|
7
|
+
import { DeliveryEngine, } from "../core/delivery-engine.js";
|
|
8
|
+
import { bytesToHex } from "wire-mesh-core/domain/device-id";
|
|
9
|
+
vi.mock("../core/identity-store.js", () => ({
|
|
10
|
+
loadRoomTokens: vi.fn(),
|
|
11
|
+
}));
|
|
12
|
+
// Opaque placeholder: markRead never inspects a token's own COSE_Sign1 structure, only forwards whatever loadRoomTokens returns to sendRoomRequestToMember.
|
|
13
|
+
const FAKE_TOKEN = "fake-token";
|
|
14
|
+
/** A device-id is a 64-character lowercase hex SHA-256 digest; room-path.ts's assertDeviceIdHex rejects anything shorter. */
|
|
15
|
+
const DEVICE_ID_HEX_LENGTH = 64;
|
|
16
|
+
const PEER_ID = "a".repeat(DEVICE_ID_HEX_LENGTH);
|
|
17
|
+
const OTHER_ID = "b".repeat(DEVICE_ID_HEX_LENGTH);
|
|
18
|
+
const THIRD_ID = "c".repeat(DEVICE_ID_HEX_LENGTH);
|
|
19
|
+
const NOW_MS = 1_700_000_000_000;
|
|
20
|
+
/** Mirrors delivery-engine.ts's own private MAX_LOCAL_DELIVERY_DEDUP_KEYS -- not exported, so re-derived here as the literal the source comment documents rather than imported. */
|
|
21
|
+
const MAX_LOCAL_DELIVERY_DEDUP_KEYS = 50;
|
|
22
|
+
/** delivery-engine.ts imports this from mesh-store-shared.ts; re-imported here directly so the queueDelivery boundary tests stay pinned to the real value rather than a re-typed guess. */
|
|
23
|
+
const MAX_QUEUED_DELIVERIES_PER_AGENT = 100;
|
|
24
|
+
/** A fresh, distinct, real device-id-shaped hex id -- avoids the no-magic-numbers problem a per-test literal byte value would run into (matching room-protocol.test.ts's own precedent), using the same randomId() utility production code already uses for the identical purpose. */
|
|
25
|
+
function messageId() {
|
|
26
|
+
return bytesToHex(randomId());
|
|
27
|
+
}
|
|
28
|
+
function room(overrides = {}) {
|
|
29
|
+
return {
|
|
30
|
+
id: "room-1",
|
|
31
|
+
version: 1,
|
|
32
|
+
name: "room-name",
|
|
33
|
+
type: "public",
|
|
34
|
+
owner: PEER_ID,
|
|
35
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
36
|
+
description: "",
|
|
37
|
+
members: [PEER_ID],
|
|
38
|
+
invited: [],
|
|
39
|
+
memberJoins: {},
|
|
40
|
+
memberLeaves: {},
|
|
41
|
+
invitedJoins: {},
|
|
42
|
+
invitedLeaves: {},
|
|
43
|
+
...overrides,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function agent(overrides = {}) {
|
|
47
|
+
return {
|
|
48
|
+
id: OTHER_ID,
|
|
49
|
+
version: 1,
|
|
50
|
+
name: "recipient",
|
|
51
|
+
harness: "pi",
|
|
52
|
+
cwd: "/tmp",
|
|
53
|
+
pid: 111,
|
|
54
|
+
startedAt: "2026-01-01T00:00:00.000Z",
|
|
55
|
+
visibility: "visible",
|
|
56
|
+
status: "active",
|
|
57
|
+
tags: [],
|
|
58
|
+
subscribedRooms: [],
|
|
59
|
+
...overrides,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function roomMessage(overrides = {}) {
|
|
63
|
+
return {
|
|
64
|
+
id: messageId(),
|
|
65
|
+
from: PEER_ID,
|
|
66
|
+
room: "room-1",
|
|
67
|
+
content: "hi",
|
|
68
|
+
timestamp: "2026-01-01T00:00:00.000Z",
|
|
69
|
+
readBy: [],
|
|
70
|
+
...overrides,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function dmMessage(overrides = {}) {
|
|
74
|
+
return {
|
|
75
|
+
id: messageId(),
|
|
76
|
+
from: PEER_ID,
|
|
77
|
+
to: OTHER_ID,
|
|
78
|
+
content: "hi",
|
|
79
|
+
timestamp: "2026-01-01T00:00:00.000Z",
|
|
80
|
+
readBy: [],
|
|
81
|
+
...overrides,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function fakeTransport() {
|
|
85
|
+
return {
|
|
86
|
+
dataPort: 4000,
|
|
87
|
+
isCoordinator: false,
|
|
88
|
+
hasCoordinatorConnection: false,
|
|
89
|
+
startDataServer: vi.fn().mockResolvedValue(undefined),
|
|
90
|
+
connectToCoordinator: vi.fn().mockResolvedValue(undefined),
|
|
91
|
+
becomeCoordinator: vi.fn().mockResolvedValue(undefined),
|
|
92
|
+
connectToPeer: vi.fn().mockResolvedValue(undefined),
|
|
93
|
+
send: vi.fn().mockResolvedValue(undefined),
|
|
94
|
+
acceptConnection: vi.fn().mockResolvedValue(undefined),
|
|
95
|
+
rejectConnection: vi.fn().mockResolvedValue(undefined),
|
|
96
|
+
connectToRemote: vi.fn().mockResolvedValue(undefined),
|
|
97
|
+
broadcast: vi.fn().mockResolvedValue(undefined),
|
|
98
|
+
broadcastRevocation: vi.fn().mockResolvedValue(undefined),
|
|
99
|
+
sendRoomRequest: vi
|
|
100
|
+
.fn()
|
|
101
|
+
.mockResolvedValue({ result: "error", code: "not_connected" }),
|
|
102
|
+
addListener: vi.fn().mockResolvedValue("listener-1"),
|
|
103
|
+
removeListener: vi.fn().mockResolvedValue(undefined),
|
|
104
|
+
listListeners: vi.fn().mockReturnValue([]),
|
|
105
|
+
shutdown: vi.fn().mockResolvedValue(undefined),
|
|
106
|
+
unref: vi.fn(),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function makeHarness() {
|
|
110
|
+
let onDelivery;
|
|
111
|
+
let onPatch;
|
|
112
|
+
let shutDown = false;
|
|
113
|
+
const transport = fakeTransport();
|
|
114
|
+
const sendRoomRequestToMember = vi.fn().mockResolvedValue(undefined);
|
|
115
|
+
const revocationRecord = vi.fn().mockResolvedValue(undefined);
|
|
116
|
+
const deps = {
|
|
117
|
+
agents: new Map(),
|
|
118
|
+
rooms: new Map(),
|
|
119
|
+
messages: new Map(),
|
|
120
|
+
dms: new Map(),
|
|
121
|
+
deliveryQueues: new Map(),
|
|
122
|
+
localDeliveryKeys: new Set(),
|
|
123
|
+
pendingMarkReadTimers: [],
|
|
124
|
+
getPeerId: () => PEER_ID,
|
|
125
|
+
requireIdentity: () => ({
|
|
126
|
+
slot: { harness: "pi", cwd: "/tmp" },
|
|
127
|
+
clock: { now: () => NOW_MS },
|
|
128
|
+
identity: {},
|
|
129
|
+
revocation: { record: revocationRecord },
|
|
130
|
+
}),
|
|
131
|
+
requireTransport: () => transport,
|
|
132
|
+
getOnDelivery: () => onDelivery,
|
|
133
|
+
getOnPatch: () => onPatch,
|
|
134
|
+
isShutDown: () => shutDown,
|
|
135
|
+
sendRoomRequestToMember,
|
|
136
|
+
};
|
|
137
|
+
return {
|
|
138
|
+
deps,
|
|
139
|
+
engine: new DeliveryEngine(deps),
|
|
140
|
+
transport,
|
|
141
|
+
sendRoomRequestToMember,
|
|
142
|
+
revocationRecord,
|
|
143
|
+
setOnDelivery(fn) {
|
|
144
|
+
onDelivery = fn;
|
|
145
|
+
},
|
|
146
|
+
setOnPatch(fn) {
|
|
147
|
+
onPatch = fn;
|
|
148
|
+
},
|
|
149
|
+
setShutDown(v) {
|
|
150
|
+
shutDown = v;
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
beforeEach(() => {
|
|
155
|
+
vi.mocked(loadRoomTokens).mockReset();
|
|
156
|
+
vi.mocked(loadRoomTokens).mockReturnValue({ "room-1": FAKE_TOKEN });
|
|
157
|
+
});
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
// Gossip callbacks
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
describe("DeliveryEngine — handlePresenceAdvert", () => {
|
|
162
|
+
it("does nothing for a device with no known agent record", () => {
|
|
163
|
+
const h = makeHarness();
|
|
164
|
+
h.engine.handlePresenceAdvert(OTHER_ID, "idle");
|
|
165
|
+
expect(h.deps.agents.size).toBe(0);
|
|
166
|
+
});
|
|
167
|
+
it("does nothing when the gossiped status matches the current one", () => {
|
|
168
|
+
const h = makeHarness();
|
|
169
|
+
h.deps.agents.set(OTHER_ID, agent({ status: "active", version: 1 }));
|
|
170
|
+
h.engine.handlePresenceAdvert(OTHER_ID, "active");
|
|
171
|
+
expect(h.deps.agents.get(OTHER_ID)?.version).toBe(1);
|
|
172
|
+
});
|
|
173
|
+
it("applies a genuine status change and bumps the agent's version", () => {
|
|
174
|
+
const h = makeHarness();
|
|
175
|
+
h.deps.agents.set(OTHER_ID, agent({ status: "active", version: 1 }));
|
|
176
|
+
h.engine.handlePresenceAdvert(OTHER_ID, "busy");
|
|
177
|
+
const updated = h.deps.agents.get(OTHER_ID);
|
|
178
|
+
expect(updated?.status).toBe("busy");
|
|
179
|
+
expect(updated?.version).toBe(2);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
describe("DeliveryEngine — handleRevocationAnnounce", () => {
|
|
183
|
+
it("records a gossiped revocation entry against this store's own identity", async () => {
|
|
184
|
+
const h = makeHarness();
|
|
185
|
+
const entry = { tokenId: "tok" };
|
|
186
|
+
await h.engine.handleRevocationAnnounce(entry);
|
|
187
|
+
expect(h.revocationRecord).toHaveBeenCalledWith(entry, {
|
|
188
|
+
identity: {},
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
// Queueing
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
describe("DeliveryEngine — queueDelivery", () => {
|
|
196
|
+
it("appends events under the target agent's queue", () => {
|
|
197
|
+
const h = makeHarness();
|
|
198
|
+
h.engine.queueDelivery(OTHER_ID, {
|
|
199
|
+
type: "member_left",
|
|
200
|
+
room: "r",
|
|
201
|
+
agent: OTHER_ID,
|
|
202
|
+
});
|
|
203
|
+
h.engine.queueDelivery(OTHER_ID, {
|
|
204
|
+
type: "member_left",
|
|
205
|
+
room: "r2",
|
|
206
|
+
agent: OTHER_ID,
|
|
207
|
+
});
|
|
208
|
+
expect(h.deps.deliveryQueues.get(OTHER_ID)).toHaveLength(2);
|
|
209
|
+
});
|
|
210
|
+
// Two mutants Stryker raises against queueDelivery's own `arr.length > MAX_QUEUED_DELIVERIES_PER_AGENT` guard are provably equivalent, not gaps: one replaces the whole condition with `true`, the other replaces `>` with `>=`. Both are unobservable because of `splice`'s own semantics, not because of a weak test -- the splice call's own deleteCount is computed as `arr.length - MAX_QUEUED_DELIVERIES_PER_AGENT`, which is <= 0 in exactly the cases these mutants would newly enter the branch for (at or below the cap), and `Array.prototype.splice` with a non-positive deleteCount is a documented no-op. So whether the branch itself fires is invisible: the guard's only effect is gating a call that already does nothing when the guard would have been false. Matches the identical reasoning already applied to room-protocol.ts's own `>` vs `>=` retry-queue boundary.
|
|
211
|
+
it("keeps exactly MAX_QUEUED_DELIVERIES_PER_AGENT entries with no eviction at the boundary", () => {
|
|
212
|
+
const h = makeHarness();
|
|
213
|
+
for (let i = 0; i < MAX_QUEUED_DELIVERIES_PER_AGENT; i++) {
|
|
214
|
+
h.engine.queueDelivery(OTHER_ID, {
|
|
215
|
+
type: "member_left",
|
|
216
|
+
room: `r${i}`,
|
|
217
|
+
agent: OTHER_ID,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
const arr = h.deps.deliveryQueues.get(OTHER_ID);
|
|
221
|
+
expect(arr).toHaveLength(MAX_QUEUED_DELIVERIES_PER_AGENT);
|
|
222
|
+
expect(arr?.[0]).toMatchObject({ room: "r0" });
|
|
223
|
+
});
|
|
224
|
+
it("evicts the oldest entry, and only the oldest, once the bound is exceeded", () => {
|
|
225
|
+
const h = makeHarness();
|
|
226
|
+
for (let i = 0; i < MAX_QUEUED_DELIVERIES_PER_AGENT + 1; i++) {
|
|
227
|
+
h.engine.queueDelivery(OTHER_ID, {
|
|
228
|
+
type: "member_left",
|
|
229
|
+
room: `r${i}`,
|
|
230
|
+
agent: OTHER_ID,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
const arr = h.deps.deliveryQueues.get(OTHER_ID);
|
|
234
|
+
expect(arr).toHaveLength(MAX_QUEUED_DELIVERIES_PER_AGENT);
|
|
235
|
+
expect(arr?.[0]).toMatchObject({ room: "r1" });
|
|
236
|
+
expect(arr?.at(-1)).toMatchObject({
|
|
237
|
+
room: `r${MAX_QUEUED_DELIVERIES_PER_AGENT}`,
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
// Membership merge (mergeRoom / refreshMembership / mergeMemberOps, reached via applyPatch/applyStateSync)
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
describe("DeliveryEngine — mergeRoom via applyPatch(room_upsert)", () => {
|
|
245
|
+
it("inserts a brand-new room and derives its membership views", async () => {
|
|
246
|
+
const h = makeHarness();
|
|
247
|
+
const incoming = room({
|
|
248
|
+
id: "new-room",
|
|
249
|
+
memberJoins: { [OTHER_ID]: 1 },
|
|
250
|
+
memberLeaves: {},
|
|
251
|
+
});
|
|
252
|
+
await h.engine.applyPatch({ type: "room_upsert", room: incoming });
|
|
253
|
+
const stored = h.deps.rooms.get("new-room");
|
|
254
|
+
expect(stored?.members).toEqual([OTHER_ID]);
|
|
255
|
+
});
|
|
256
|
+
it("drops a stale room_upsert patch whose version is strictly behind the local copy", async () => {
|
|
257
|
+
const h = makeHarness();
|
|
258
|
+
h.deps.rooms.set("room-1", room({ version: 5, name: "current" }));
|
|
259
|
+
await h.engine.applyPatch({
|
|
260
|
+
type: "room_upsert",
|
|
261
|
+
room: room({ version: 3, name: "stale" }),
|
|
262
|
+
});
|
|
263
|
+
expect(h.deps.rooms.get("room-1")?.name).toBe("current");
|
|
264
|
+
});
|
|
265
|
+
it("takes the strictly higher of the two versions, never the lower, on a genuine merge", async () => {
|
|
266
|
+
const h = makeHarness();
|
|
267
|
+
h.deps.rooms.set("room-1", room({ version: 1 }));
|
|
268
|
+
await h.engine.applyPatch({
|
|
269
|
+
type: "room_upsert",
|
|
270
|
+
room: room({ version: 2 }),
|
|
271
|
+
});
|
|
272
|
+
expect(h.deps.rooms.get("room-1")?.version).toBe(2);
|
|
273
|
+
});
|
|
274
|
+
it("merges rather than dropping a room_upsert patch at exactly the same version as the local copy", async () => {
|
|
275
|
+
const h = makeHarness();
|
|
276
|
+
h.deps.rooms.set("room-1", room({ version: 2, name: "old-name" }));
|
|
277
|
+
await h.engine.applyPatch({
|
|
278
|
+
type: "room_upsert",
|
|
279
|
+
room: room({ version: 2, name: "new-name" }),
|
|
280
|
+
});
|
|
281
|
+
expect(h.deps.rooms.get("room-1")?.name).toBe("new-name");
|
|
282
|
+
});
|
|
283
|
+
it("retains the existing federated flag when the incoming room leaves it undefined", async () => {
|
|
284
|
+
const h = makeHarness();
|
|
285
|
+
h.deps.rooms.set("room-1", room({ version: 1, federated: true }));
|
|
286
|
+
const incoming = room({ version: 2 });
|
|
287
|
+
delete incoming.federated;
|
|
288
|
+
await h.engine.applyPatch({ type: "room_upsert", room: incoming });
|
|
289
|
+
expect(h.deps.rooms.get("room-1")?.federated).toBe(true);
|
|
290
|
+
});
|
|
291
|
+
it("overwrites the federated flag when the incoming room states it explicitly, including false", async () => {
|
|
292
|
+
const h = makeHarness();
|
|
293
|
+
h.deps.rooms.set("room-1", room({ version: 1, federated: true }));
|
|
294
|
+
await h.engine.applyPatch({
|
|
295
|
+
type: "room_upsert",
|
|
296
|
+
room: room({ version: 2, federated: false }),
|
|
297
|
+
});
|
|
298
|
+
expect(h.deps.rooms.get("room-1")?.federated).toBe(false);
|
|
299
|
+
});
|
|
300
|
+
it("merges concurrent memberJoins by keeping the higher revision per agent, never overwriting with a lower one", async () => {
|
|
301
|
+
const h = makeHarness();
|
|
302
|
+
h.deps.rooms.set("room-1", room({ version: 1, memberJoins: { [OTHER_ID]: 2 } }));
|
|
303
|
+
await h.engine.applyPatch({
|
|
304
|
+
type: "room_upsert",
|
|
305
|
+
room: room({ version: 2, memberJoins: { [OTHER_ID]: 1, [THIRD_ID]: 1 } }),
|
|
306
|
+
});
|
|
307
|
+
const stored = h.deps.rooms.get("room-1");
|
|
308
|
+
expect(stored?.memberJoins[OTHER_ID]).toBe(2);
|
|
309
|
+
expect(stored?.memberJoins[THIRD_ID]).toBe(1);
|
|
310
|
+
});
|
|
311
|
+
// mergeMemberOps' own `stamp > (merged[id] ?? 0)` boundary check has one provably equivalent mutant Stryker still raises here: replacing `>` with `>=`. It's unobservable because the two operators only disagree when `stamp === merged[id]` -- and at that exact point, `merged[id] = stamp` assigns the map entry its own current value, which produces no difference any assertion could detect. The test above already proves the operator's real job (a strictly lower incoming stamp never overwrites a higher local one, and a strictly higher one does); the equal-stamp case has nothing further to distinguish.
|
|
312
|
+
});
|
|
313
|
+
describe("DeliveryEngine — refreshMembership", () => {
|
|
314
|
+
it("derives members as exactly those whose latest join outranks their latest leave", () => {
|
|
315
|
+
const h = makeHarness();
|
|
316
|
+
const r = room({
|
|
317
|
+
memberJoins: { [OTHER_ID]: 2, [THIRD_ID]: 3 },
|
|
318
|
+
memberLeaves: { [OTHER_ID]: 2, [THIRD_ID]: 1 },
|
|
319
|
+
});
|
|
320
|
+
h.engine.refreshMembership(r);
|
|
321
|
+
expect(r.members).toEqual([THIRD_ID]);
|
|
322
|
+
});
|
|
323
|
+
it("derives invited as exactly those whose latest invite-join outranks their latest invite-leave", () => {
|
|
324
|
+
const h = makeHarness();
|
|
325
|
+
const r = room({
|
|
326
|
+
invitedJoins: { [OTHER_ID]: 4, [THIRD_ID]: 2 },
|
|
327
|
+
invitedLeaves: { [OTHER_ID]: 1, [THIRD_ID]: 2 },
|
|
328
|
+
});
|
|
329
|
+
h.engine.refreshMembership(r);
|
|
330
|
+
expect(r.invited).toEqual([OTHER_ID]);
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
// ---------------------------------------------------------------------------
|
|
334
|
+
// bump
|
|
335
|
+
// ---------------------------------------------------------------------------
|
|
336
|
+
describe("DeliveryEngine — bump", () => {
|
|
337
|
+
it("increments the entity's version and returns the same reference", () => {
|
|
338
|
+
const h = makeHarness();
|
|
339
|
+
const r = room({ version: 1 });
|
|
340
|
+
const result = h.engine.bump(r);
|
|
341
|
+
expect(result).toBe(r);
|
|
342
|
+
expect(r.version).toBe(2);
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
// ---------------------------------------------------------------------------
|
|
346
|
+
// applyStateSync
|
|
347
|
+
// ---------------------------------------------------------------------------
|
|
348
|
+
function emptyState(overrides = {}) {
|
|
349
|
+
return {
|
|
350
|
+
agents: {},
|
|
351
|
+
rooms: {},
|
|
352
|
+
messages: {},
|
|
353
|
+
dms: {},
|
|
354
|
+
deliveryQueues: {},
|
|
355
|
+
...overrides,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
describe("DeliveryEngine — applyStateSync agents", () => {
|
|
359
|
+
it("skips an incoming agent strictly behind the local version, keeping the local copy", () => {
|
|
360
|
+
const h = makeHarness();
|
|
361
|
+
h.deps.agents.set(OTHER_ID, agent({ version: 5, name: "local" }));
|
|
362
|
+
h.engine.applyStateSync(emptyState({
|
|
363
|
+
agents: { [OTHER_ID]: agent({ version: 3, name: "stale" }) },
|
|
364
|
+
}));
|
|
365
|
+
expect(h.deps.agents.get(OTHER_ID)?.name).toBe("local");
|
|
366
|
+
});
|
|
367
|
+
it("unions subscribedRooms without duplication when the incoming agent is at the exact same version", () => {
|
|
368
|
+
const h = makeHarness();
|
|
369
|
+
h.deps.agents.set(OTHER_ID, agent({ version: 5, subscribedRooms: ["local-room", "shared-room"] }));
|
|
370
|
+
h.engine.applyStateSync(emptyState({
|
|
371
|
+
agents: {
|
|
372
|
+
[OTHER_ID]: agent({
|
|
373
|
+
version: 5,
|
|
374
|
+
subscribedRooms: ["incoming-room", "shared-room"],
|
|
375
|
+
}),
|
|
376
|
+
},
|
|
377
|
+
}));
|
|
378
|
+
const merged = h.deps.agents.get(OTHER_ID);
|
|
379
|
+
expect(merged?.subscribedRooms.sort()).toEqual(["incoming-room", "local-room", "shared-room"].sort());
|
|
380
|
+
});
|
|
381
|
+
it("replaces wholesale, with no union, when the incoming agent is at a strictly higher version", () => {
|
|
382
|
+
const h = makeHarness();
|
|
383
|
+
h.deps.agents.set(OTHER_ID, agent({ version: 5, subscribedRooms: ["local-only"] }));
|
|
384
|
+
h.engine.applyStateSync(emptyState({
|
|
385
|
+
agents: {
|
|
386
|
+
[OTHER_ID]: agent({ version: 9, subscribedRooms: ["incoming-only"] }),
|
|
387
|
+
},
|
|
388
|
+
}));
|
|
389
|
+
expect(h.deps.agents.get(OTHER_ID)?.subscribedRooms).toEqual([
|
|
390
|
+
"incoming-only",
|
|
391
|
+
]);
|
|
392
|
+
});
|
|
393
|
+
it("inserts a brand-new agent this store has never seen", () => {
|
|
394
|
+
const h = makeHarness();
|
|
395
|
+
h.engine.applyStateSync(emptyState({ agents: { [OTHER_ID]: agent({ version: 1 }) } }));
|
|
396
|
+
expect(h.deps.agents.has(OTHER_ID)).toBe(true);
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
describe("DeliveryEngine — applyStateSync rooms", () => {
|
|
400
|
+
it("skips an incoming room strictly behind the local version", () => {
|
|
401
|
+
const h = makeHarness();
|
|
402
|
+
h.deps.rooms.set("room-1", room({ version: 5, name: "current" }));
|
|
403
|
+
h.engine.applyStateSync(emptyState({ rooms: { "room-1": room({ version: 2, name: "stale" }) } }));
|
|
404
|
+
expect(h.deps.rooms.get("room-1")?.name).toBe("current");
|
|
405
|
+
});
|
|
406
|
+
it("merges an incoming room at or above the local version", () => {
|
|
407
|
+
const h = makeHarness();
|
|
408
|
+
h.deps.rooms.set("room-1", room({ version: 5 }));
|
|
409
|
+
h.engine.applyStateSync(emptyState({
|
|
410
|
+
rooms: { "room-1": room({ version: 5, name: "renamed" }) },
|
|
411
|
+
}));
|
|
412
|
+
expect(h.deps.rooms.get("room-1")?.name).toBe("renamed");
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
describe("DeliveryEngine — applyStateSync message/dm histories", () => {
|
|
416
|
+
it("adopts the incoming history unmodified when this room has no local messages yet", () => {
|
|
417
|
+
const h = makeHarness();
|
|
418
|
+
const msgs = [roomMessage({ id: messageId() })];
|
|
419
|
+
h.engine.applyStateSync(emptyState({ messages: { "room-1": msgs } }));
|
|
420
|
+
expect(h.deps.messages.get("room-1")).toEqual(msgs);
|
|
421
|
+
});
|
|
422
|
+
it("merges the incoming history with the local one when both already have messages", () => {
|
|
423
|
+
const h = makeHarness();
|
|
424
|
+
const localId = messageId();
|
|
425
|
+
const incomingId = messageId();
|
|
426
|
+
h.deps.messages.set("room-1", [roomMessage({ id: localId })]);
|
|
427
|
+
h.engine.applyStateSync(emptyState({
|
|
428
|
+
messages: { "room-1": [roomMessage({ id: incomingId })] },
|
|
429
|
+
}));
|
|
430
|
+
expect(h.deps.messages
|
|
431
|
+
.get("room-1")
|
|
432
|
+
?.map((m) => m.id)
|
|
433
|
+
.sort()).toEqual([localId, incomingId].sort());
|
|
434
|
+
});
|
|
435
|
+
it("adopts the incoming dm history unmodified when there is no local copy yet", () => {
|
|
436
|
+
const h = makeHarness();
|
|
437
|
+
const msgs = [dmMessage({ id: messageId() })];
|
|
438
|
+
h.engine.applyStateSync(emptyState({ dms: { "dm-key": msgs } }));
|
|
439
|
+
expect(h.deps.dms.get("dm-key")).toEqual(msgs);
|
|
440
|
+
});
|
|
441
|
+
it("merges the incoming dm history with the local one when both already have entries", () => {
|
|
442
|
+
const h = makeHarness();
|
|
443
|
+
const localId = messageId();
|
|
444
|
+
const incomingId = messageId();
|
|
445
|
+
h.deps.dms.set("dm-key", [dmMessage({ id: localId })]);
|
|
446
|
+
h.engine.applyStateSync(emptyState({ dms: { "dm-key": [dmMessage({ id: incomingId })] } }));
|
|
447
|
+
expect(h.deps.dms
|
|
448
|
+
.get("dm-key")
|
|
449
|
+
?.map((m) => m.id)
|
|
450
|
+
.sort()).toEqual([localId, incomingId].sort());
|
|
451
|
+
});
|
|
452
|
+
});
|
|
453
|
+
// One mutant Stryker still raises in this block is a provable equivalent, not a gap: the `(this.deps.deliveryQueues.get(agentId) ?? []).map((e) => JSON.stringify(e))` line building the dedup "seen" set has its own `?? []` fallback replaced with `?? ["Stryker was here"]`. That fallback only runs when the local queue for agentId is undefined, and mapping it into `JSON.stringify("Stryker was here")` produces a garbage string no real DeliveryEvent's own JSON serialisation will ever collide with -- so the seen set ends up functionally empty either way, and the dedup test below (which already proves a genuinely-repeated event is skipped and a genuinely-new one isn't) can't distinguish an empty seen set from one seeded with an unreachable placeholder string.
|
|
454
|
+
describe("DeliveryEngine — applyStateSync deliveryQueues replay", () => {
|
|
455
|
+
it("queues every incoming event, even ones with no replay-eligible type", () => {
|
|
456
|
+
const h = makeHarness();
|
|
457
|
+
h.engine.applyStateSync(emptyState({
|
|
458
|
+
deliveryQueues: {
|
|
459
|
+
[PEER_ID]: [
|
|
460
|
+
{
|
|
461
|
+
type: "member_status",
|
|
462
|
+
room: "room-1",
|
|
463
|
+
agent: OTHER_ID,
|
|
464
|
+
status: "idle",
|
|
465
|
+
},
|
|
466
|
+
],
|
|
467
|
+
},
|
|
468
|
+
}));
|
|
469
|
+
expect(h.deps.deliveryQueues.get(PEER_ID)).toHaveLength(1);
|
|
470
|
+
});
|
|
471
|
+
it("does not duplicate an event already present in the local queue", () => {
|
|
472
|
+
const h = makeHarness();
|
|
473
|
+
const event = {
|
|
474
|
+
type: "member_left",
|
|
475
|
+
room: "room-1",
|
|
476
|
+
agent: OTHER_ID,
|
|
477
|
+
};
|
|
478
|
+
h.deps.deliveryQueues.set(PEER_ID, [event]);
|
|
479
|
+
h.engine.applyStateSync(emptyState({ deliveryQueues: { [PEER_ID]: [event] } }));
|
|
480
|
+
expect(h.deps.deliveryQueues.get(PEER_ID)).toHaveLength(1);
|
|
481
|
+
});
|
|
482
|
+
it("fires onDelivery for a replayed room_message targeting this peer's own agent", () => {
|
|
483
|
+
const h = makeHarness();
|
|
484
|
+
const delivered = vi.fn();
|
|
485
|
+
h.setOnDelivery(delivered);
|
|
486
|
+
const event = {
|
|
487
|
+
type: "room_message",
|
|
488
|
+
message: roomMessage(),
|
|
489
|
+
};
|
|
490
|
+
h.engine.applyStateSync(emptyState({ deliveryQueues: { [PEER_ID]: [event] } }));
|
|
491
|
+
expect(delivered).toHaveBeenCalledWith(PEER_ID, event);
|
|
492
|
+
});
|
|
493
|
+
it("fires onDelivery for a replayed dm targeting this peer's own agent", () => {
|
|
494
|
+
const h = makeHarness();
|
|
495
|
+
const delivered = vi.fn();
|
|
496
|
+
h.setOnDelivery(delivered);
|
|
497
|
+
const event = { type: "dm", message: dmMessage() };
|
|
498
|
+
h.engine.applyStateSync(emptyState({ deliveryQueues: { [PEER_ID]: [event] } }));
|
|
499
|
+
expect(delivered).toHaveBeenCalledWith(PEER_ID, event);
|
|
500
|
+
});
|
|
501
|
+
it("does not fire onDelivery for a replayed transient event even though it is still queued", () => {
|
|
502
|
+
const h = makeHarness();
|
|
503
|
+
const delivered = vi.fn();
|
|
504
|
+
h.setOnDelivery(delivered);
|
|
505
|
+
const event = {
|
|
506
|
+
type: "member_status",
|
|
507
|
+
room: "room-1",
|
|
508
|
+
agent: OTHER_ID,
|
|
509
|
+
status: "idle",
|
|
510
|
+
};
|
|
511
|
+
h.engine.applyStateSync(emptyState({ deliveryQueues: { [PEER_ID]: [event] } }));
|
|
512
|
+
expect(delivered).not.toHaveBeenCalled();
|
|
513
|
+
expect(h.deps.deliveryQueues.get(PEER_ID)).toHaveLength(1);
|
|
514
|
+
});
|
|
515
|
+
it("fires onDelivery for a room_invite still reflected in the room's own invited list", () => {
|
|
516
|
+
const h = makeHarness();
|
|
517
|
+
h.deps.rooms.set("room-1", room({ invited: [PEER_ID] }));
|
|
518
|
+
const delivered = vi.fn();
|
|
519
|
+
h.setOnDelivery(delivered);
|
|
520
|
+
const event = {
|
|
521
|
+
type: "room_invite",
|
|
522
|
+
room: "room-1",
|
|
523
|
+
roomDescription: "",
|
|
524
|
+
from: OTHER_ID,
|
|
525
|
+
fromName: "other",
|
|
526
|
+
fromCwd: "/tmp",
|
|
527
|
+
};
|
|
528
|
+
h.engine.applyStateSync(emptyState({ deliveryQueues: { [PEER_ID]: [event] } }));
|
|
529
|
+
expect(delivered).toHaveBeenCalledWith(PEER_ID, event);
|
|
530
|
+
});
|
|
531
|
+
it("does not fire onDelivery for a room_invite no longer reflected in the room's invited list, though it stays queued", () => {
|
|
532
|
+
const h = makeHarness();
|
|
533
|
+
h.deps.rooms.set("room-1", room({ invited: [] }));
|
|
534
|
+
const delivered = vi.fn();
|
|
535
|
+
h.setOnDelivery(delivered);
|
|
536
|
+
const event = {
|
|
537
|
+
type: "room_invite",
|
|
538
|
+
room: "room-1",
|
|
539
|
+
roomDescription: "",
|
|
540
|
+
from: OTHER_ID,
|
|
541
|
+
fromName: "other",
|
|
542
|
+
fromCwd: "/tmp",
|
|
543
|
+
};
|
|
544
|
+
h.engine.applyStateSync(emptyState({ deliveryQueues: { [PEER_ID]: [event] } }));
|
|
545
|
+
expect(delivered).not.toHaveBeenCalled();
|
|
546
|
+
expect(h.deps.deliveryQueues.get(PEER_ID)).toHaveLength(1);
|
|
547
|
+
});
|
|
548
|
+
});
|
|
549
|
+
// ---------------------------------------------------------------------------
|
|
550
|
+
// applyPatch — non-delivery cases (agent_upsert / room_delete / message_add / dm_add) -- the delivery case, and everything after it, lives in delivery-engine-delivery.test.ts
|
|
551
|
+
// ---------------------------------------------------------------------------
|
|
552
|
+
describe("DeliveryEngine — applyPatch(agent_upsert)", () => {
|
|
553
|
+
it("drops a stale patch whose version is strictly behind the local copy", async () => {
|
|
554
|
+
const h = makeHarness();
|
|
555
|
+
h.deps.agents.set(OTHER_ID, agent({ version: 5, name: "current" }));
|
|
556
|
+
await h.engine.applyPatch({
|
|
557
|
+
type: "agent_upsert",
|
|
558
|
+
agent: agent({ version: 3, name: "stale" }),
|
|
559
|
+
});
|
|
560
|
+
expect(h.deps.agents.get(OTHER_ID)?.name).toBe("current");
|
|
561
|
+
});
|
|
562
|
+
it("inserts a brand-new agent with no prior local record", async () => {
|
|
563
|
+
const h = makeHarness();
|
|
564
|
+
await h.engine.applyPatch({
|
|
565
|
+
type: "agent_upsert",
|
|
566
|
+
agent: agent({ version: 1 }),
|
|
567
|
+
});
|
|
568
|
+
expect(h.deps.agents.has(OTHER_ID)).toBe(true);
|
|
569
|
+
});
|
|
570
|
+
it("unions subscribedRooms without duplication at the exact same version", async () => {
|
|
571
|
+
const h = makeHarness();
|
|
572
|
+
h.deps.agents.set(OTHER_ID, agent({ version: 4, subscribedRooms: ["local-only", "shared"] }));
|
|
573
|
+
await h.engine.applyPatch({
|
|
574
|
+
type: "agent_upsert",
|
|
575
|
+
agent: agent({
|
|
576
|
+
version: 4,
|
|
577
|
+
subscribedRooms: ["incoming-only", "shared"],
|
|
578
|
+
}),
|
|
579
|
+
});
|
|
580
|
+
const merged = h.deps.agents.get(OTHER_ID);
|
|
581
|
+
expect(merged?.subscribedRooms.sort()).toEqual(["incoming-only", "local-only", "shared"].sort());
|
|
582
|
+
});
|
|
583
|
+
});
|
|
584
|
+
// The `this.deps.agents.set(patch.agentId, agent)` call at the end of this branch has one provable equivalent mutant Stryker still raises: removing it entirely. `agent` here is fetched via `this.deps.agents.get(patch.agentId)`, the exact same object reference already stored in the Map, and `agent.status = "offline"` mutates that object in place -- so re-setting the map entry to the identical reference it already holds is a genuine no-op, the same Map.set-same-reference pattern already documented elsewhere in this codebase (agent-registry.ts's setAgentOffline, federation-bridge.ts's onAgentGone/onRoomLeave). The test below already proves the real, observable effect (status flips to "offline").
|
|
585
|
+
describe("DeliveryEngine — applyPatch(agent_offline)", () => {
|
|
586
|
+
it("marks an existing agent as offline", async () => {
|
|
587
|
+
const h = makeHarness();
|
|
588
|
+
h.deps.agents.set(OTHER_ID, agent({ status: "active" }));
|
|
589
|
+
await h.engine.applyPatch({ type: "agent_offline", agentId: OTHER_ID });
|
|
590
|
+
expect(h.deps.agents.get(OTHER_ID)?.status).toBe("offline");
|
|
591
|
+
});
|
|
592
|
+
it("does nothing when the agent has no local record", async () => {
|
|
593
|
+
const h = makeHarness();
|
|
594
|
+
await expect(h.engine.applyPatch({ type: "agent_offline", agentId: OTHER_ID })).resolves.toBeUndefined();
|
|
595
|
+
expect(h.deps.agents.has(OTHER_ID)).toBe(false);
|
|
596
|
+
});
|
|
597
|
+
});
|
|
598
|
+
describe("DeliveryEngine — applyPatch(room_delete/message_add/dm_add)", () => {
|
|
599
|
+
it("removes the room from the local map", async () => {
|
|
600
|
+
const h = makeHarness();
|
|
601
|
+
h.deps.rooms.set("room-1", room());
|
|
602
|
+
await h.engine.applyPatch({ type: "room_delete", roomId: "room-1" });
|
|
603
|
+
expect(h.deps.rooms.has("room-1")).toBe(false);
|
|
604
|
+
});
|
|
605
|
+
it("appends a message_add patch's message onto the room's history", async () => {
|
|
606
|
+
const h = makeHarness();
|
|
607
|
+
const msg = roomMessage({ id: messageId() });
|
|
608
|
+
await h.engine.applyPatch({
|
|
609
|
+
type: "message_add",
|
|
610
|
+
roomId: "room-1",
|
|
611
|
+
message: msg,
|
|
612
|
+
});
|
|
613
|
+
expect(h.deps.messages.get("room-1")).toEqual([msg]);
|
|
614
|
+
});
|
|
615
|
+
it("appends a dm_add patch's message onto the dm history keyed by its key", async () => {
|
|
616
|
+
const h = makeHarness();
|
|
617
|
+
const msg = dmMessage({ id: messageId() });
|
|
618
|
+
await h.engine.applyPatch({ type: "dm_add", key: "dm-key", message: msg });
|
|
619
|
+
expect(h.deps.dms.get("dm-key")).toEqual([msg]);
|
|
620
|
+
});
|
|
621
|
+
});
|
|
622
|
+
describe("DeliveryEngine — applyPatch onPatch callback", () => {
|
|
623
|
+
it("invokes onPatch with the exact patch after applying it, when one is set", async () => {
|
|
624
|
+
const h = makeHarness();
|
|
625
|
+
const onPatch = vi.fn();
|
|
626
|
+
h.setOnPatch(onPatch);
|
|
627
|
+
const patch = { type: "room_delete", roomId: "room-1" };
|
|
628
|
+
await h.engine.applyPatch(patch);
|
|
629
|
+
expect(onPatch).toHaveBeenCalledWith(patch);
|
|
630
|
+
});
|
|
631
|
+
it("does not throw when no onPatch callback is set", async () => {
|
|
632
|
+
const h = makeHarness();
|
|
633
|
+
await expect(h.engine.applyPatch({ type: "room_delete", roomId: "room-1" })).resolves.toBeUndefined();
|
|
634
|
+
});
|
|
635
|
+
});
|
|
636
|
+
describe("DeliveryEngine — deliver", () => {
|
|
637
|
+
it("delegates to deliverLocallyAndBroadcast, queueing and broadcasting the event", async () => {
|
|
638
|
+
const h = makeHarness();
|
|
639
|
+
await h.engine.deliver(OTHER_ID, {
|
|
640
|
+
type: "member_left",
|
|
641
|
+
room: "room-1",
|
|
642
|
+
agent: OTHER_ID,
|
|
643
|
+
});
|
|
644
|
+
expect(h.deps.deliveryQueues.get(OTHER_ID)).toHaveLength(1);
|
|
645
|
+
expect(h.transport.broadcast).toHaveBeenCalledTimes(1);
|
|
646
|
+
});
|
|
647
|
+
});
|
|
648
|
+
describe("DeliveryEngine — drainDelivery", () => {
|
|
649
|
+
it("returns the queued events and empties the queue", async () => {
|
|
650
|
+
const h = makeHarness();
|
|
651
|
+
const event = {
|
|
652
|
+
type: "member_left",
|
|
653
|
+
room: "room-1",
|
|
654
|
+
agent: OTHER_ID,
|
|
655
|
+
};
|
|
656
|
+
h.deps.deliveryQueues.set(PEER_ID, [event]);
|
|
657
|
+
const drained = await h.engine.drainDelivery(PEER_ID);
|
|
658
|
+
expect(drained).toEqual([event]);
|
|
659
|
+
expect(h.deps.deliveryQueues.get(PEER_ID)).toEqual([]);
|
|
660
|
+
});
|
|
661
|
+
it("returns an empty array for an agent with no queued events", async () => {
|
|
662
|
+
const h = makeHarness();
|
|
663
|
+
const drained = await h.engine.drainDelivery(OTHER_ID);
|
|
664
|
+
expect(drained).toEqual([]);
|
|
665
|
+
});
|
|
666
|
+
it("marks a drained dm as read via markRead, mirroring the room_message case", async () => {
|
|
667
|
+
const h = makeHarness();
|
|
668
|
+
const msg = dmMessage({ id: messageId(), from: OTHER_ID, readBy: [] });
|
|
669
|
+
h.deps.dms.set("self:z", [msg]);
|
|
670
|
+
vi.mocked(loadRoomTokens).mockReturnValue({ "self:z": FAKE_TOKEN });
|
|
671
|
+
h.deps.deliveryQueues.set(PEER_ID, [{ type: "dm", message: msg }]);
|
|
672
|
+
await h.engine.drainDelivery(PEER_ID);
|
|
673
|
+
expect(msg.readBy).toEqual([PEER_ID]);
|
|
674
|
+
expect(h.sendRoomRequestToMember).toHaveBeenCalledWith(OTHER_ID, "self:z", FAKE_TOKEN, expect.objectContaining({ verb: "room.read" }));
|
|
675
|
+
});
|
|
676
|
+
it("does not attempt to mark read a transient event with no message of its own", async () => {
|
|
677
|
+
const h = makeHarness();
|
|
678
|
+
h.deps.deliveryQueues.set(PEER_ID, [
|
|
679
|
+
{
|
|
680
|
+
type: "member_status",
|
|
681
|
+
room: "room-1",
|
|
682
|
+
agent: OTHER_ID,
|
|
683
|
+
status: "idle",
|
|
684
|
+
},
|
|
685
|
+
]);
|
|
686
|
+
await expect(h.engine.drainDelivery(PEER_ID)).resolves.toHaveLength(1);
|
|
687
|
+
expect(h.sendRoomRequestToMember).not.toHaveBeenCalled();
|
|
688
|
+
});
|
|
689
|
+
});
|
|
690
|
+
//# sourceMappingURL=delivery-engine.test.js.map
|