socketduct 0.1.25 → 0.1.27

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 (58) hide show
  1. package/AGENTS.md +2 -2
  2. package/changelog.d/20261010130509-bound-log-frame-compression-memory.md +1 -0
  3. package/changelog.d/20261025070000-reclaim-unreadable-retention-lock.md +14 -0
  4. package/docs/runbooks/jsonl-integrity-rollout.md +8 -6
  5. package/package.json +2 -2
  6. package/scripts/test.js +9 -14
  7. package/src/logging/compressed-log-writer.js +1 -1
  8. package/src/logging/log-retention.js +36 -1
  9. package/src/logging/zstd-frame-codec.js +15 -1
  10. package/test/e2e/gateway-process.test.js +93 -92
  11. package/test/e2e/logger-shutdown-process.test.js +40 -37
  12. package/test/e2e/reverse-client-recovery-process.test.js +107 -103
  13. package/test/e2e/reverse-gateway-process.test.js +328 -318
  14. package/test/integration/initial-target-recovery.test.js +333 -334
  15. package/test/integration/logging-cli.test.js +121 -119
  16. package/test/integration/logging-compressed-recovery.test.js +96 -95
  17. package/test/integration/multiplexed-compatibility.test.js +178 -177
  18. package/test/integration/multiplexed-corrections.test.js +360 -359
  19. package/test/integration/multiplexed-failures.test.js +253 -251
  20. package/test/integration/multiplexed-transport-set.test.js +334 -322
  21. package/test/integration/multiplexed-transport.test.js +135 -134
  22. package/test/integration/reverse-gateway-stream-id-reset.test.js +90 -89
  23. package/test/integration/reverse-gateway.test.js +2309 -2218
  24. package/test/integration/reverse-resume-continuity.test.js +219 -218
  25. package/test/integration/reverse-route-class.test.js +358 -341
  26. package/test/integration/socketduct-http-agent.test.js +314 -312
  27. package/test/integration/socketduct-https-agent.test.js +516 -485
  28. package/test/integration/stream-compression.test.js +862 -824
  29. package/test/integration/tcp-forwarding.test.js +2550 -2447
  30. package/test/integration/tls-forwarding.test.js +593 -592
  31. package/test/unit/auth/auth.test.js +43 -44
  32. package/test/unit/auth/static-token-store.test.js +9 -8
  33. package/test/unit/client/connect.test.js +41 -40
  34. package/test/unit/client/deferred-virtual-socket.test.js +95 -94
  35. package/test/unit/client/multiplexed-spool-budget.test.js +155 -154
  36. package/test/unit/client/multiplexed-transport-set.test.js +830 -748
  37. package/test/unit/client/socketduct-socket.test.js +768 -771
  38. package/test/unit/client/virtual-socket.test.js +198 -168
  39. package/test/unit/config/docker-gateway-files.test.js +224 -224
  40. package/test/unit/config/environment.test.js +472 -492
  41. package/test/unit/index.test.js +53 -52
  42. package/test/unit/logging/compressed-log-worker.test.js +23 -22
  43. package/test/unit/logging/compressed-log-writer.test.js +155 -154
  44. package/test/unit/logging/log-retention.test.js +71 -48
  45. package/test/unit/logging/log-segment-index.test.js +84 -81
  46. package/test/unit/logging/zstd-frame-codec.test.js +28 -19
  47. package/test/unit/protocol/compression.test.js +254 -298
  48. package/test/unit/protocol/frame.test.js +280 -276
  49. package/test/unit/protocol/v3-compression.test.js +21 -23
  50. package/test/unit/protocol/v3-frame.test.js +42 -41
  51. package/test/unit/release-configuration.test.js +15 -12
  52. package/test/unit/reverse/disconnect-attribution.test.js +65 -64
  53. package/test/unit/reverse-terminal-outcomes.test.js +53 -50
  54. package/test/unit/server/control-handler.test.js +232 -231
  55. package/test/unit/server/primary.test.js +51 -49
  56. package/test/unit/server/relay-runtime.test.js +280 -294
  57. package/test/unit/sessions/session.test.js +48 -62
  58. 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
  })