socketduct 0.1.25 → 0.1.26

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.
Files changed (56) hide show
  1. package/AGENTS.md +2 -2
  2. package/changelog.d/20261010130509-bound-log-frame-compression-memory.md +1 -0
  3. package/docs/runbooks/jsonl-integrity-rollout.md +8 -6
  4. package/package.json +2 -2
  5. package/scripts/test.js +9 -14
  6. package/src/logging/compressed-log-writer.js +1 -1
  7. package/src/logging/zstd-frame-codec.js +15 -1
  8. package/test/e2e/gateway-process.test.js +93 -92
  9. package/test/e2e/logger-shutdown-process.test.js +40 -37
  10. package/test/e2e/reverse-client-recovery-process.test.js +107 -103
  11. package/test/e2e/reverse-gateway-process.test.js +328 -318
  12. package/test/integration/initial-target-recovery.test.js +333 -334
  13. package/test/integration/logging-cli.test.js +121 -119
  14. package/test/integration/logging-compressed-recovery.test.js +96 -95
  15. package/test/integration/multiplexed-compatibility.test.js +178 -177
  16. package/test/integration/multiplexed-corrections.test.js +360 -359
  17. package/test/integration/multiplexed-failures.test.js +253 -251
  18. package/test/integration/multiplexed-transport-set.test.js +334 -322
  19. package/test/integration/multiplexed-transport.test.js +135 -134
  20. package/test/integration/reverse-gateway-stream-id-reset.test.js +90 -89
  21. package/test/integration/reverse-gateway.test.js +2309 -2218
  22. package/test/integration/reverse-resume-continuity.test.js +219 -218
  23. package/test/integration/reverse-route-class.test.js +358 -341
  24. package/test/integration/socketduct-http-agent.test.js +314 -312
  25. package/test/integration/socketduct-https-agent.test.js +516 -485
  26. package/test/integration/stream-compression.test.js +862 -824
  27. package/test/integration/tcp-forwarding.test.js +2550 -2447
  28. package/test/integration/tls-forwarding.test.js +593 -592
  29. package/test/unit/auth/auth.test.js +43 -44
  30. package/test/unit/auth/static-token-store.test.js +9 -8
  31. package/test/unit/client/connect.test.js +41 -40
  32. package/test/unit/client/deferred-virtual-socket.test.js +95 -94
  33. package/test/unit/client/multiplexed-spool-budget.test.js +155 -154
  34. package/test/unit/client/multiplexed-transport-set.test.js +830 -748
  35. package/test/unit/client/socketduct-socket.test.js +768 -771
  36. package/test/unit/client/virtual-socket.test.js +198 -168
  37. package/test/unit/config/docker-gateway-files.test.js +224 -224
  38. package/test/unit/config/environment.test.js +472 -492
  39. package/test/unit/index.test.js +53 -52
  40. package/test/unit/logging/compressed-log-worker.test.js +23 -22
  41. package/test/unit/logging/compressed-log-writer.test.js +155 -154
  42. package/test/unit/logging/log-retention.test.js +52 -47
  43. package/test/unit/logging/log-segment-index.test.js +84 -81
  44. package/test/unit/logging/zstd-frame-codec.test.js +28 -19
  45. package/test/unit/protocol/compression.test.js +254 -298
  46. package/test/unit/protocol/frame.test.js +280 -276
  47. package/test/unit/protocol/v3-compression.test.js +21 -23
  48. package/test/unit/protocol/v3-frame.test.js +42 -41
  49. package/test/unit/release-configuration.test.js +15 -12
  50. package/test/unit/reverse/disconnect-attribution.test.js +65 -64
  51. package/test/unit/reverse-terminal-outcomes.test.js +53 -50
  52. package/test/unit/server/control-handler.test.js +232 -231
  53. package/test/unit/server/primary.test.js +51 -49
  54. package/test/unit/server/relay-runtime.test.js +280 -294
  55. package/test/unit/sessions/session.test.js +48 -62
  56. package/test/unit/spool/spool-file.test.js +401 -385
@@ -1,5 +1,4 @@
1
- import assert from "node:assert/strict"
2
- import test from "node:test"
1
+ import {describe, expect, it} from "@velocious/testing"
3
2
 
4
3
  import {InMemoryUserStore, login, TokenStore} from "../../../src/auth/user-store.js"
5
4
  import {handleControlMessage as rawHandleControlMessage} from "../../../src/server/control-handler.js"
@@ -26,266 +25,268 @@ function handleControlMessage(options, message) {
26
25
  return rawHandleControlMessage({...options, supportedFraming: [2]}, {...message, framing: [2]})
27
26
  }
28
27
 
29
- test("handleControlMessage requires auth before creating sessions", () => {
30
- const response = handleControlMessage({tokens: new TokenStore(), sessions: new SessionStore()}, {
31
- type: "session.create",
32
- name: "laptop shell"
28
+ describe("control handler", () => {
29
+ it("handleControlMessage requires auth before creating sessions", () => {
30
+ const response = handleControlMessage({tokens: new TokenStore(), sessions: new SessionStore()}, {
31
+ type: "session.create",
32
+ name: "laptop shell"
33
+ })
34
+
35
+ expect(response).toEqual({
36
+ type: "error",
37
+ code: "AUTH_REQUIRED",
38
+ message: "Authentication is required.",
39
+ retryable: false
40
+ })
33
41
  })
34
42
 
35
- assert.deepEqual(response, {
36
- type: "error",
37
- code: "AUTH_REQUIRED",
38
- message: "Authentication is required.",
39
- retryable: false
43
+ it("handleControlMessage creates a named session for authenticated users", async () => {
44
+ const {token, tokens, sessions} = await createAuthenticatedStores()
45
+
46
+ const response = handleControlMessage({tokens, sessions, authorizeTarget: () => true}, {
47
+ type: "session.create",
48
+ token: token.value,
49
+ name: "laptop shell",
50
+ identifier: "session-123",
51
+ target: {host: "127.0.0.1", port: 5432}
52
+ })
53
+
54
+ expect(response).toEqual({
55
+ type: "session.created",
56
+ identifier: "session-123",
57
+ name: "laptop shell",
58
+ state: "creating",
59
+ target: {host: "127.0.0.1", port: 5432},
60
+ framing: 2
61
+ })
40
62
  })
41
- })
42
-
43
- test("handleControlMessage creates a named session for authenticated users", async () => {
44
- const {token, tokens, sessions} = await createAuthenticatedStores()
45
63
 
46
- const response = handleControlMessage({tokens, sessions, authorizeTarget: () => true}, {
47
- type: "session.create",
48
- token: token.value,
49
- name: "laptop shell",
50
- identifier: "session-123",
51
- target: {host: "127.0.0.1", port: 5432}
64
+ it("handleControlMessage returns a protocol error for duplicate session identifiers", async () => {
65
+ const {token, tokens, sessions} = await createAuthenticatedStores()
66
+ const message = {
67
+ type: "session.create",
68
+ token: token.value,
69
+ name: "laptop shell",
70
+ identifier: "session-123",
71
+ target: {host: "127.0.0.1", port: 5432}
72
+ }
73
+
74
+ expect(handleControlMessage({tokens, sessions, authorizeTarget: () => true}, message).type).toBe("session.created")
75
+ expect(handleControlMessage({tokens, sessions, authorizeTarget: () => true}, message)).toEqual({
76
+ type: "error",
77
+ code: "SESSION_EXISTS",
78
+ message: "Session identifier already exists.",
79
+ retryable: false
80
+ })
52
81
  })
53
82
 
54
- assert.deepEqual(response, {
55
- type: "session.created",
56
- identifier: "session-123",
57
- name: "laptop shell",
58
- state: "creating",
59
- target: {host: "127.0.0.1", port: 5432},
60
- framing: 2
83
+ it("handleControlMessage resumes only sessions owned by the authenticated user", async () => {
84
+ const {token, tokens, sessions} = await createAuthenticatedStores()
85
+ const created = handleControlMessage({tokens, sessions, authorizeTarget: () => true}, {
86
+ type: "session.create",
87
+ token: token.value,
88
+ name: "laptop shell",
89
+ identifier: "session-123",
90
+ target: {host: "127.0.0.1", port: 5432}
91
+ })
92
+ expect(created.type).toBe("session.created")
93
+ sessions.transition("session-123", "active")
94
+ sessions.updateSequences("session-123", {clientSequence: 42, relaySequence: 7})
95
+ sessions.transition("session-123", "interrupted")
96
+
97
+ const resumed = handleControlMessage({tokens, sessions, authorizeTarget: () => true}, {
98
+ type: "session.resume",
99
+ token: token.value,
100
+ identifier: "session-123"
101
+ })
102
+
103
+ expect(resumed).toEqual({
104
+ type: "session.resumed",
105
+ identifier: "session-123",
106
+ name: "laptop shell",
107
+ state: "resuming",
108
+ serverLastSequence: 7,
109
+ serverLastAck: 42,
110
+ replayFromSequence: 43,
111
+ target: {host: "127.0.0.1", port: 5432},
112
+ framing: 2
113
+ })
61
114
  })
62
- })
63
115
 
64
- test("handleControlMessage returns a protocol error for duplicate session identifiers", async () => {
65
- const {token, tokens, sessions} = await createAuthenticatedStores()
66
- const message = {
67
- type: "session.create",
68
- token: token.value,
69
- name: "laptop shell",
70
- identifier: "session-123",
71
- target: {host: "127.0.0.1", port: 5432}
72
- }
73
-
74
- assert.equal(handleControlMessage({tokens, sessions, authorizeTarget: () => true}, message).type, "session.created")
75
- assert.deepEqual(handleControlMessage({tokens, sessions, authorizeTarget: () => true}, message), {
76
- type: "error",
77
- code: "SESSION_EXISTS",
78
- message: "Session identifier already exists.",
79
- retryable: false
116
+ it("handleControlMessage rejects missing or malformed session targets", async () => {
117
+ const {token, tokens, sessions} = await createAuthenticatedStores()
118
+
119
+ expect(handleControlMessage({tokens, sessions, authorizeTarget: () => true}, {
120
+ type: "session.create",
121
+ token: token.value,
122
+ name: "missing target"
123
+ })).toEqual({
124
+ type: "error",
125
+ code: "PROTOCOL_VIOLATION",
126
+ message: "Target host and port are required.",
127
+ retryable: false
128
+ })
129
+
130
+ expect(handleControlMessage({tokens, sessions, authorizeTarget: () => true}, {
131
+ type: "session.create",
132
+ token: token.value,
133
+ name: "bad target",
134
+ target: {host: "", port: 0}
135
+ })).toEqual({
136
+ type: "error",
137
+ code: "PROTOCOL_VIOLATION",
138
+ message: "Target host and port are required.",
139
+ retryable: false
140
+ })
80
141
  })
81
- })
82
142
 
83
- test("handleControlMessage resumes only sessions owned by the authenticated user", async () => {
84
- const {token, tokens, sessions} = await createAuthenticatedStores()
85
- const created = handleControlMessage({tokens, sessions, authorizeTarget: () => true}, {
86
- type: "session.create",
87
- token: token.value,
88
- name: "laptop shell",
89
- identifier: "session-123",
90
- target: {host: "127.0.0.1", port: 5432}
91
- })
92
- assert.equal(created.type, "session.created")
93
- sessions.transition("session-123", "active")
94
- sessions.updateSequences("session-123", {clientSequence: 42, relaySequence: 7})
95
- sessions.transition("session-123", "interrupted")
96
-
97
- const resumed = handleControlMessage({tokens, sessions, authorizeTarget: () => true}, {
98
- type: "session.resume",
99
- token: token.value,
100
- identifier: "session-123"
143
+ it("handleControlMessage rejects unauthorized targets", async () => {
144
+ const {token, tokens, sessions} = await createAuthenticatedStores()
145
+
146
+ const response = handleControlMessage({
147
+ tokens,
148
+ sessions,
149
+ authorizeTarget: () => false
150
+ }, {
151
+ type: "session.create",
152
+ token: token.value,
153
+ name: "blocked target",
154
+ target: {host: "10.0.0.1", port: 22}
155
+ })
156
+
157
+ expect(response).toEqual({
158
+ type: "error",
159
+ code: "TARGET_UNAUTHORIZED",
160
+ message: "Target is not authorized for this principal.",
161
+ retryable: false
162
+ })
101
163
  })
102
164
 
103
- assert.deepEqual(resumed, {
104
- type: "session.resumed",
105
- identifier: "session-123",
106
- name: "laptop shell",
107
- state: "resuming",
108
- serverLastSequence: 7,
109
- serverLastAck: 42,
110
- replayFromSequence: 43,
111
- target: {host: "127.0.0.1", port: 5432},
112
- framing: 2
113
- })
114
- })
165
+ it("handleControlMessage selects a mutually supported stream.data codec on create", async () => {
166
+ const {token, tokens, sessions} = await createAuthenticatedStores()
115
167
 
116
- test("handleControlMessage rejects missing or malformed session targets", async () => {
117
- const {token, tokens, sessions} = await createAuthenticatedStores()
118
-
119
- assert.deepEqual(handleControlMessage({tokens, sessions, authorizeTarget: () => true}, {
120
- type: "session.create",
121
- token: token.value,
122
- name: "missing target"
123
- }), {
124
- type: "error",
125
- code: "PROTOCOL_VIOLATION",
126
- message: "Target host and port are required.",
127
- retryable: false
128
- })
129
-
130
- assert.deepEqual(handleControlMessage({tokens, sessions, authorizeTarget: () => true}, {
131
- type: "session.create",
132
- token: token.value,
133
- name: "bad target",
134
- target: {host: "", port: 0}
135
- }), {
136
- type: "error",
137
- code: "PROTOCOL_VIOLATION",
138
- message: "Target host and port are required.",
139
- retryable: false
140
- })
141
- })
168
+ const response = handleControlMessage({tokens, sessions, authorizeTarget: () => true, supportedCodecs: ["zstd"]}, {
169
+ type: "session.create",
170
+ token: token.value,
171
+ name: "compressed",
172
+ target: {host: "127.0.0.1", port: 5432},
173
+ compression: ["zstd"]
174
+ })
142
175
 
143
- test("handleControlMessage rejects unauthorized targets", async () => {
144
- const {token, tokens, sessions} = await createAuthenticatedStores()
145
-
146
- const response = handleControlMessage({
147
- tokens,
148
- sessions,
149
- authorizeTarget: () => false
150
- }, {
151
- type: "session.create",
152
- token: token.value,
153
- name: "blocked target",
154
- target: {host: "10.0.0.1", port: 22}
176
+ expect(response.type).toBe("session.created")
177
+ expect(response.compression).toBe("zstd")
155
178
  })
156
179
 
157
- assert.deepEqual(response, {
158
- type: "error",
159
- code: "TARGET_UNAUTHORIZED",
160
- message: "Target is not authorized for this principal.",
161
- retryable: false
162
- })
163
- })
180
+ it("handleControlMessage omits compression for a legacy client that offers none", async () => {
181
+ const {token, tokens, sessions} = await createAuthenticatedStores()
164
182
 
165
- test("handleControlMessage selects a mutually supported stream.data codec on create", async () => {
166
- const {token, tokens, sessions} = await createAuthenticatedStores()
183
+ const response = handleControlMessage({tokens, sessions, authorizeTarget: () => true, supportedCodecs: ["zstd"]}, {
184
+ type: "session.create",
185
+ token: token.value,
186
+ name: "legacy",
187
+ target: {host: "127.0.0.1", port: 5432}
188
+ })
167
189
 
168
- const response = handleControlMessage({tokens, sessions, authorizeTarget: () => true, supportedCodecs: ["zstd"]}, {
169
- type: "session.create",
170
- token: token.value,
171
- name: "compressed",
172
- target: {host: "127.0.0.1", port: 5432},
173
- compression: ["zstd"]
190
+ expect(response.type).toBe("session.created")
191
+ expect("compression" in response).toBe(false)
174
192
  })
175
193
 
176
- assert.equal(response.type, "session.created")
177
- assert.equal(response.compression, "zstd")
178
- })
194
+ it("handleControlMessage omits compression when the relay supports no codec", async () => {
195
+ const {token, tokens, sessions} = await createAuthenticatedStores()
179
196
 
180
- test("handleControlMessage omits compression for a legacy client that offers none", async () => {
181
- const {token, tokens, sessions} = await createAuthenticatedStores()
197
+ const response = handleControlMessage({tokens, sessions, authorizeTarget: () => true, supportedCodecs: []}, {
198
+ type: "session.create",
199
+ token: token.value,
200
+ name: "disabled",
201
+ target: {host: "127.0.0.1", port: 5432},
202
+ compression: ["zstd"]
203
+ })
182
204
 
183
- const response = handleControlMessage({tokens, sessions, authorizeTarget: () => true, supportedCodecs: ["zstd"]}, {
184
- type: "session.create",
185
- token: token.value,
186
- name: "legacy",
187
- target: {host: "127.0.0.1", port: 5432}
205
+ expect(response.type).toBe("session.created")
206
+ expect("compression" in response).toBe(false)
188
207
  })
189
208
 
190
- assert.equal(response.type, "session.created")
191
- assert.equal("compression" in response, false)
192
- })
209
+ it("handleControlMessage omits compression when no offered codec is supported", async () => {
210
+ const {token, tokens, sessions} = await createAuthenticatedStores()
193
211
 
194
- test("handleControlMessage omits compression when the relay supports no codec", async () => {
195
- const {token, tokens, sessions} = await createAuthenticatedStores()
212
+ const response = handleControlMessage({tokens, sessions, authorizeTarget: () => true, supportedCodecs: ["zstd"]}, {
213
+ type: "session.create",
214
+ token: token.value,
215
+ name: "unknown codec",
216
+ target: {host: "127.0.0.1", port: 5432},
217
+ compression: ["brotli"]
218
+ })
196
219
 
197
- const response = handleControlMessage({tokens, sessions, authorizeTarget: () => true, supportedCodecs: []}, {
198
- type: "session.create",
199
- token: token.value,
200
- name: "disabled",
201
- target: {host: "127.0.0.1", port: 5432},
202
- compression: ["zstd"]
220
+ expect(response.type).toBe("session.created")
221
+ expect("compression" in response).toBe(false)
203
222
  })
204
223
 
205
- assert.equal(response.type, "session.created")
206
- assert.equal("compression" in response, false)
207
- })
208
-
209
- test("handleControlMessage omits compression when no offered codec is supported", async () => {
210
- const {token, tokens, sessions} = await createAuthenticatedStores()
211
-
212
- const response = handleControlMessage({tokens, sessions, authorizeTarget: () => true, supportedCodecs: ["zstd"]}, {
213
- type: "session.create",
214
- token: token.value,
215
- name: "unknown codec",
216
- target: {host: "127.0.0.1", port: 5432},
217
- compression: ["brotli"]
224
+ it("handleControlMessage renegotiates the stream.data codec on resume", async () => {
225
+ const {token, tokens, sessions} = await createAuthenticatedStores()
226
+
227
+ const created = handleControlMessage({tokens, sessions, authorizeTarget: () => true, supportedCodecs: ["zstd"]}, {
228
+ type: "session.create",
229
+ token: token.value,
230
+ name: "resumable",
231
+ target: {host: "127.0.0.1", port: 5432},
232
+ compression: ["zstd"]
233
+ })
234
+ expect(created.type).toBe("session.created")
235
+ const identifier = /** @type {string} */ (created.identifier)
236
+ sessions.transition(identifier, "active")
237
+
238
+ const resumed = handleControlMessage({tokens, sessions, authorizeTarget: () => true, supportedCodecs: ["zstd"]}, {
239
+ type: "session.resume",
240
+ token: token.value,
241
+ identifier,
242
+ compression: ["zstd"]
243
+ })
244
+
245
+ expect(resumed.type).toBe("session.resumed")
246
+ expect(resumed.compression).toBe("zstd")
218
247
  })
219
248
 
220
- assert.equal(response.type, "session.created")
221
- assert.equal("compression" in response, false)
222
- })
223
-
224
- test("handleControlMessage renegotiates the stream.data codec on resume", async () => {
225
- const {token, tokens, sessions} = await createAuthenticatedStores()
226
-
227
- const created = handleControlMessage({tokens, sessions, authorizeTarget: () => true, supportedCodecs: ["zstd"]}, {
228
- type: "session.create",
229
- token: token.value,
230
- name: "resumable",
231
- target: {host: "127.0.0.1", port: 5432},
232
- compression: ["zstd"]
233
- })
234
- assert.equal(created.type, "session.created")
235
- const identifier = /** @type {string} */ (created.identifier)
236
- sessions.transition(identifier, "active")
237
-
238
- const resumed = handleControlMessage({tokens, sessions, authorizeTarget: () => true, supportedCodecs: ["zstd"]}, {
239
- type: "session.resume",
240
- token: token.value,
241
- identifier,
242
- compression: ["zstd"]
249
+ it("handleControlMessage negotiates compact framing v2 only when offered and supported", async () => {
250
+ const first = await createAuthenticatedStores()
251
+ const base = {type: "session.create", token: first.token.value, name: "compact", target: {host: "127.0.0.1", port: 80}}
252
+ const selected = rawHandleControlMessage({tokens: first.tokens, sessions: first.sessions, authorizeTarget: () => true, supportedFraming: [2]}, {...base, framing: [2]})
253
+ expect(selected.framing).toBe(2)
254
+
255
+ const second = await createAuthenticatedStores()
256
+ const missing = rawHandleControlMessage({tokens: second.tokens, sessions: second.sessions, authorizeTarget: () => true, supportedFraming: [2]}, {...base, token: second.token.value, identifier: "missing-framing"})
257
+ expect(missing.type).toBe("error")
258
+ expect(missing.code).toBe("PROTOCOL_VIOLATION")
259
+ expect(second.sessions.get("missing-framing")).toBeUndefined()
260
+
261
+ const third = await createAuthenticatedStores()
262
+ const legacy = rawHandleControlMessage({tokens: third.tokens, sessions: third.sessions, authorizeTarget: () => true, supportedFraming: [2]}, {...base, token: third.token.value, identifier: "legacy-framing", framing: [1]})
263
+ expect(legacy.type).toBe("error")
264
+ expect(legacy.code).toBe("PROTOCOL_VIOLATION")
265
+ expect(third.sessions.get("legacy-framing")).toBeUndefined()
243
266
  })
244
267
 
245
- assert.equal(resumed.type, "session.resumed")
246
- assert.equal(resumed.compression, "zstd")
247
- })
248
-
249
- test("handleControlMessage negotiates compact framing v2 only when offered and supported", async () => {
250
- const first = await createAuthenticatedStores()
251
- const base = {type: "session.create", token: first.token.value, name: "compact", target: {host: "127.0.0.1", port: 80}}
252
- const selected = rawHandleControlMessage({tokens: first.tokens, sessions: first.sessions, authorizeTarget: () => true, supportedFraming: [2]}, {...base, framing: [2]})
253
- assert.equal(selected.framing, 2)
254
-
255
- const second = await createAuthenticatedStores()
256
- const missing = rawHandleControlMessage({tokens: second.tokens, sessions: second.sessions, authorizeTarget: () => true, supportedFraming: [2]}, {...base, token: second.token.value, identifier: "missing-framing"})
257
- assert.equal(missing.type, "error")
258
- assert.equal(missing.code, "PROTOCOL_VIOLATION")
259
- assert.equal(second.sessions.get("missing-framing"), undefined)
260
-
261
- const third = await createAuthenticatedStores()
262
- const legacy = rawHandleControlMessage({tokens: third.tokens, sessions: third.sessions, authorizeTarget: () => true, supportedFraming: [2]}, {...base, token: third.token.value, identifier: "legacy-framing", framing: [1]})
263
- assert.equal(legacy.type, "error")
264
- assert.equal(legacy.code, "PROTOCOL_VIOLATION")
265
- assert.equal(third.sessions.get("legacy-framing"), undefined)
266
- })
267
-
268
- test("handleControlMessage rejects legacy resume before authentication or session mutation", async () => {
269
- const {token, tokens, sessions} = await createAuthenticatedStores()
270
- handleControlMessage({tokens, sessions, authorizeTarget: () => true}, {
271
- type: "session.create",
272
- token: token.value,
273
- name: "resumable",
274
- identifier: "resume-framing",
275
- target: {host: "127.0.0.1", port: 80}
268
+ it("handleControlMessage rejects legacy resume before authentication or session mutation", async () => {
269
+ const {token, tokens, sessions} = await createAuthenticatedStores()
270
+ handleControlMessage({tokens, sessions, authorizeTarget: () => true}, {
271
+ type: "session.create",
272
+ token: token.value,
273
+ name: "resumable",
274
+ identifier: "resume-framing",
275
+ target: {host: "127.0.0.1", port: 80}
276
+ })
277
+ sessions.transition("resume-framing", "active")
278
+ sessions.transition("resume-framing", "interrupted")
279
+
280
+ let authenticationCalls = 0
281
+ const response = rawHandleControlMessage({
282
+ tokens: {authenticate: () => { authenticationCalls += 1; return {userId: "ignored", username: "ignored"} }},
283
+ sessions,
284
+ supportedFraming: [2]
285
+ }, {type: "session.resume", token: "ignored", identifier: "resume-framing", framing: [1]})
286
+
287
+ expect(response.type).toBe("error")
288
+ expect(response.code).toBe("PROTOCOL_VIOLATION")
289
+ expect(authenticationCalls).toBe(0)
290
+ expect(sessions.get("resume-framing")?.state).toBe("interrupted")
276
291
  })
277
- sessions.transition("resume-framing", "active")
278
- sessions.transition("resume-framing", "interrupted")
279
-
280
- let authenticationCalls = 0
281
- const response = rawHandleControlMessage({
282
- tokens: {authenticate: () => { authenticationCalls += 1; return {userId: "ignored", username: "ignored"} }},
283
- sessions,
284
- supportedFraming: [2]
285
- }, {type: "session.resume", token: "ignored", identifier: "resume-framing", framing: [1]})
286
-
287
- assert.equal(response.type, "error")
288
- assert.equal(response.code, "PROTOCOL_VIOLATION")
289
- assert.equal(authenticationCalls, 0)
290
- assert.equal(sessions.get("resume-framing")?.state, "interrupted")
291
292
  })
@@ -1,59 +1,61 @@
1
- import assert from "node:assert/strict"
2
- import test from "node:test"
1
+ import {describe, expect, it} from "@velocious/testing"
3
2
 
4
3
  import {resolveWorkerCount, startPrimary} from "../../../src/server/primary.js"
5
4
 
6
- test("resolveWorkerCount uses configured workers or available CPUs", () => {
7
- assert.equal(resolveWorkerCount(3, () => 99), 3)
8
- assert.equal(resolveWorkerCount(undefined, () => 4), 4)
9
- assert.equal(resolveWorkerCount(undefined, () => 0), 1)
10
- assert.throws(() => resolveWorkerCount(0), /Invalid worker count/)
11
- })
5
+ describe("primary", () => {
6
+ it("resolveWorkerCount uses configured workers or available CPUs", () => {
7
+ expect(resolveWorkerCount(3, () => 99)).toBe(3)
8
+ expect(resolveWorkerCount(undefined, () => 4)).toBe(4)
9
+ expect(resolveWorkerCount(undefined, () => 0)).toBe(1)
10
+ expect(() => resolveWorkerCount(0)).toThrow(/Invalid worker count/)
11
+ })
12
12
 
13
- test("startPrimary forks configured workers and restarts exits", () => {
14
- /** @type {Array<Record<string, string> | undefined>} */
15
- const forks = []
16
- /** @type {undefined | ((worker: {id: number}, code: number | null, signal: string | null) => void)} */
17
- let exitListener
18
- /** @type {string[]} */
19
- const warnings = []
20
-
21
- const result = startPrimary({
22
- workers: 2,
23
- clusterApi: {
24
- isPrimary: true,
25
- fork: (env) => {
26
- forks.push(env)
27
- return {id: forks.length}
13
+ it("startPrimary forks configured workers and restarts exits", () => {
14
+ /** @type {Array<Record<string, string> | undefined>} */
15
+ const forks = []
16
+ /** @type {undefined | ((worker: {id: number}, code: number | null, signal: string | null) => void)} */
17
+ let exitListener
18
+ /** @type {string[]} */
19
+ const warnings = []
20
+
21
+ const result = startPrimary({
22
+ workers: 2,
23
+ clusterApi: {
24
+ isPrimary: true,
25
+ fork: (env) => {
26
+ forks.push(env)
27
+ return {id: forks.length}
28
+ },
29
+ on: (_event, listener) => {
30
+ exitListener = listener
31
+ }
28
32
  },
29
- on: (_event, listener) => {
30
- exitListener = listener
31
- }
32
- },
33
- logger: {warn: (message) => warnings.push(message)}
33
+ logger: {warn: (message) => warnings.push(message)}
34
+ })
35
+
36
+ expect(result).toEqual({role: "primary", workers: 2})
37
+ expect(forks).toEqual([{SOCKETDUCT_WORKER_INDEX: "0"}, {SOCKETDUCT_WORKER_INDEX: "1"}])
38
+
39
+ expect(exitListener).toBeDefined()
40
+ if (exitListener === undefined) throw new Error("Expected an exit listener")
41
+ exitListener({id: 7}, 1, null)
42
+ expect(forks.length).toBe(3)
43
+ expect(warnings.length).toBe(1)
44
+ expect(String(warnings[0])).toMatch(/worker 7 exited/)
34
45
  })
35
46
 
36
- assert.deepEqual(result, {role: "primary", workers: 2})
37
- assert.deepEqual(forks, [{SOCKETDUCT_WORKER_INDEX: "0"}, {SOCKETDUCT_WORKER_INDEX: "1"}])
38
-
39
- assert.ok(exitListener)
40
- exitListener({id: 7}, 1, null)
41
- assert.equal(forks.length, 3)
42
- assert.equal(warnings.length, 1)
43
- assert.match(String(warnings[0]), /worker 7 exited/)
44
- })
47
+ it("startPrimary reports worker role without forking", () => {
48
+ const result = startPrimary({
49
+ workers: 2,
50
+ clusterApi: {
51
+ isPrimary: false,
52
+ fork: () => {
53
+ throw new Error("should not fork from worker")
54
+ },
55
+ on: () => {}
56
+ }
57
+ })
45
58
 
46
- test("startPrimary reports worker role without forking", () => {
47
- const result = startPrimary({
48
- workers: 2,
49
- clusterApi: {
50
- isPrimary: false,
51
- fork: () => {
52
- throw new Error("should not fork from worker")
53
- },
54
- on: () => {}
55
- }
59
+ expect(result).toEqual({role: "worker", workers: 0})
56
60
  })
57
-
58
- assert.deepEqual(result, {role: "worker", workers: 0})
59
61
  })