Skip to content

Commit 2802ed4

Browse files
zenglingbiaoclaude
andcommitted
test(gateway): add unit tests for node wake state tracking and testing seam
Add 17 unit tests for nodes-wake-state.ts covering: - Exported wait/poll constants - nodeWakeById Map operations (insert, overwrite, multi-entry, inFlight) - nodeWakeNudgeById Map operations (independent tracking) - clearNodeWakeState function (removal, no-op, scoped deletion) - testing seam (getNodeWakeByIdSize, hasNodeWakeEntry, resetWakeState) Co-Authored-By: Claude Opus 4.8 <[email protected]>
1 parent 6cb82ea commit 2802ed4

1 file changed

Lines changed: 136 additions & 0 deletions

File tree

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Tests for node wake state tracking and testing seam.
2+
import { afterEach, describe, expect, it } from "vitest";
3+
import {
4+
NODE_WAKE_RECONNECT_WAIT_MS,
5+
NODE_WAKE_RECONNECT_RETRY_WAIT_MS,
6+
NODE_WAKE_RECONNECT_POLL_MS,
7+
clearNodeWakeState,
8+
nodeWakeById,
9+
nodeWakeNudgeById,
10+
testing,
11+
} from "./nodes-wake-state.js";
12+
13+
afterEach(() => {
14+
testing.resetWakeState();
15+
});
16+
17+
describe("constants", () => {
18+
it("exports expected wait/poll constants", () => {
19+
expect(NODE_WAKE_RECONNECT_WAIT_MS).toBe(3_000);
20+
expect(NODE_WAKE_RECONNECT_RETRY_WAIT_MS).toBe(12_000);
21+
expect(NODE_WAKE_RECONNECT_POLL_MS).toBe(150);
22+
});
23+
});
24+
25+
describe("nodeWakeById", () => {
26+
it("starts empty", () => {
27+
expect(nodeWakeById.size).toBe(0);
28+
});
29+
30+
it("stores NodeWakeState entries", () => {
31+
const now = Date.now();
32+
nodeWakeById.set("node-1", { lastWakeAtMs: now });
33+
expect(nodeWakeById.size).toBe(1);
34+
expect(nodeWakeById.get("node-1")?.lastWakeAtMs).toBe(now);
35+
});
36+
37+
it("stores multiple entries", () => {
38+
nodeWakeById.set("a", { lastWakeAtMs: 100 });
39+
nodeWakeById.set("b", { lastWakeAtMs: 200 });
40+
nodeWakeById.set("c", { lastWakeAtMs: 300 });
41+
expect(nodeWakeById.size).toBe(3);
42+
});
43+
44+
it("overwrites existing entry when key is reused", () => {
45+
nodeWakeById.set("node-1", { lastWakeAtMs: 100 });
46+
nodeWakeById.set("node-1", { lastWakeAtMs: 200 });
47+
expect(nodeWakeById.size).toBe(1);
48+
});
49+
50+
it("supports inFlight promise property", () => {
51+
const promise = Promise.resolve({
52+
available: true,
53+
throttled: false,
54+
path: "sent" as const,
55+
durationMs: 50,
56+
});
57+
nodeWakeById.set("node-1", { lastWakeAtMs: Date.now(), inFlight: promise });
58+
expect(nodeWakeById.get("node-1")?.inFlight).toBe(promise);
59+
});
60+
});
61+
62+
describe("nodeWakeNudgeById", () => {
63+
it("starts empty", () => {
64+
expect(nodeWakeNudgeById.size).toBe(0);
65+
});
66+
67+
it("stores nudge timestamps", () => {
68+
nodeWakeNudgeById.set("node-1", 1000);
69+
expect(nodeWakeNudgeById.size).toBe(1);
70+
expect(nodeWakeNudgeById.get("node-1")).toBe(1000);
71+
});
72+
73+
it("independently tracked from nodeWakeById", () => {
74+
nodeWakeById.set("node-1", { lastWakeAtMs: 500 });
75+
nodeWakeNudgeById.set("node-1", 1000);
76+
expect(nodeWakeById.size).toBe(1);
77+
expect(nodeWakeNudgeById.size).toBe(1);
78+
});
79+
});
80+
81+
describe("clearNodeWakeState", () => {
82+
it("removes the wake entry and nudge for the given node", () => {
83+
nodeWakeById.set("node-1", { lastWakeAtMs: 100 });
84+
nodeWakeNudgeById.set("node-1", 200);
85+
clearNodeWakeState("node-1");
86+
expect(nodeWakeById.has("node-1")).toBe(false);
87+
expect(nodeWakeNudgeById.has("node-1")).toBe(false);
88+
});
89+
90+
it("is a no-op when the node id does not exist", () => {
91+
expect(() => clearNodeWakeState("ghost")).not.toThrow();
92+
expect(nodeWakeById.size).toBe(0);
93+
expect(nodeWakeNudgeById.size).toBe(0);
94+
});
95+
96+
it("only removes the specified node, leaving others intact", () => {
97+
nodeWakeById.set("a", { lastWakeAtMs: 1 });
98+
nodeWakeById.set("b", { lastWakeAtMs: 2 });
99+
nodeWakeNudgeById.set("a", 10);
100+
nodeWakeNudgeById.set("b", 20);
101+
clearNodeWakeState("a");
102+
expect(nodeWakeById.has("a")).toBe(false);
103+
expect(nodeWakeById.has("b")).toBe(true);
104+
expect(nodeWakeNudgeById.has("a")).toBe(false);
105+
expect(nodeWakeNudgeById.has("b")).toBe(true);
106+
});
107+
});
108+
109+
describe("testing seam", () => {
110+
it("getNodeWakeByIdSize returns 0 when nothing is stored", () => {
111+
expect(testing.getNodeWakeByIdSize()).toBe(0);
112+
});
113+
114+
it("getNodeWakeByIdSize reflects inserted entries", () => {
115+
nodeWakeById.set("x", { lastWakeAtMs: 1 });
116+
nodeWakeById.set("y", { lastWakeAtMs: 2 });
117+
expect(testing.getNodeWakeByIdSize()).toBe(2);
118+
});
119+
120+
it("hasNodeWakeEntry returns true for stored ids", () => {
121+
nodeWakeById.set("present", { lastWakeAtMs: 1 });
122+
expect(testing.hasNodeWakeEntry("present")).toBe(true);
123+
});
124+
125+
it("hasNodeWakeEntry returns false for unknown ids", () => {
126+
expect(testing.hasNodeWakeEntry("missing")).toBe(false);
127+
});
128+
129+
it("resetWakeState clears both maps", () => {
130+
nodeWakeById.set("a", { lastWakeAtMs: 1 });
131+
nodeWakeNudgeById.set("a", 100);
132+
testing.resetWakeState();
133+
expect(nodeWakeById.size).toBe(0);
134+
expect(nodeWakeNudgeById.size).toBe(0);
135+
});
136+
});

0 commit comments

Comments
 (0)