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 {
5
4
  createPasswordHash,
@@ -11,54 +10,54 @@ import {
11
10
  TokenStore
12
11
  } from "../../../src/auth/user-store.js"
13
12
 
14
- test("createPasswordHash stores a salted scrypt hash instead of plaintext", async () => {
15
- const hash = await createPasswordHash("correct horse battery staple")
13
+ describe("auth", () => {
14
+ it("createPasswordHash stores a salted scrypt hash instead of plaintext", async () => {
15
+ const hash = await createPasswordHash("correct horse battery staple")
16
16
 
17
- assert.notEqual(hash, "correct horse battery staple")
18
- assert.match(hash, /^scrypt\$/)
19
- assert.equal(await verifyPassword("correct horse battery staple", hash), true)
20
- assert.equal(await verifyPassword("wrong password", hash), false)
21
- })
22
-
23
- test("login returns an opaque token for valid username and password", async () => {
24
- const users = new InMemoryUserStore()
25
- const tokens = new TokenStore({now: () => 1_000})
26
- await users.createUser({username: "kasper", password: "secret-password"})
27
-
28
- const token = await login(users, tokens, {
29
- username: "kasper",
30
- password: "secret-password"
17
+ expect(hash).not.toBe("correct horse battery staple")
18
+ expect(hash).toMatch(/^scrypt\$/)
19
+ expect(await verifyPassword("correct horse battery staple", hash)).toBe(true)
20
+ expect(await verifyPassword("wrong password", hash)).toBe(false)
31
21
  })
32
22
 
33
- assert.match(token.value, /^[a-zA-Z0-9_-]{32,}$/)
34
- assert.equal(tokens.authenticate(token.value)?.username, "kasper")
35
- })
23
+ it("login returns an opaque token for valid username and password", async () => {
24
+ const users = new InMemoryUserStore()
25
+ const tokens = new TokenStore({now: () => 1_000})
26
+ await users.createUser({username: "kasper", password: "secret-password"})
36
27
 
37
- test("login rejects invalid username or password", async () => {
38
- const users = new InMemoryUserStore()
39
- const tokens = new TokenStore()
40
- await users.createUser({username: "kasper", password: "secret-password"})
28
+ const token = await login(users, tokens, {
29
+ username: "kasper",
30
+ password: "secret-password"
31
+ })
41
32
 
42
- await assert.rejects(
43
- login(users, tokens, {username: "missing", password: "secret-password"}),
44
- /invalid username or password/i
45
- )
46
- await assert.rejects(
47
- login(users, tokens, {username: "kasper", password: "wrong-password"}),
48
- /invalid username or password/i
49
- )
50
- })
33
+ expect(token.value).toMatch(/^[a-zA-Z0-9_-]{32,}$/)
34
+ expect(tokens.authenticate(token.value)?.username).toBe("kasper")
35
+ })
36
+
37
+ it("login rejects invalid username or password", async () => {
38
+ const users = new InMemoryUserStore()
39
+ const tokens = new TokenStore()
40
+ await users.createUser({username: "kasper", password: "secret-password"})
41
+
42
+ await expect(
43
+ login(users, tokens, {username: "missing", password: "secret-password"})
44
+ ).rejects.toThrow(/invalid username or password/i)
45
+ await expect(
46
+ login(users, tokens, {username: "kasper", password: "wrong-password"})
47
+ ).rejects.toThrow(/invalid username or password/i)
48
+ })
51
49
 
52
- test("TokenStore expires and revokes tokens", () => {
53
- let now = 1_000
54
- const tokens = new TokenStore({now: () => now, tokenTtlMs: 500})
55
- const token = tokens.issue({userId: "user-1", username: "kasper"})
50
+ it("TokenStore expires and revokes tokens", () => {
51
+ let now = 1_000
52
+ const tokens = new TokenStore({now: () => now, tokenTtlMs: 500})
53
+ const token = tokens.issue({userId: "user-1", username: "kasper"})
56
54
 
57
- assert.equal(tokens.authenticate(token.value)?.userId, "user-1")
58
- now = 1_501
59
- assert.equal(tokens.authenticate(token.value), undefined)
55
+ expect(tokens.authenticate(token.value)?.userId).toBe("user-1")
56
+ now = 1_501
57
+ expect(tokens.authenticate(token.value)).toBeUndefined()
60
58
 
61
- const revoked = tokens.issue({userId: "user-1", username: "kasper"})
62
- assert.equal(tokens.revoke(revoked.value), true)
63
- assert.equal(tokens.authenticate(revoked.value), undefined)
59
+ const revoked = tokens.issue({userId: "user-1", username: "kasper"})
60
+ expect(tokens.revoke(revoked.value)).toBe(true)
61
+ expect(tokens.authenticate(revoked.value)).toBeUndefined()
62
+ })
64
63
  })
@@ -1,14 +1,15 @@
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 {StaticTokenStore} from "../../../src/auth/static-token-store.js"
5
4
 
6
- test("StaticTokenStore authenticates configured bearer tokens", () => {
7
- const tokens = new StaticTokenStore([{username: "edge", token: "secret-token"}])
5
+ describe("static token store", () => {
6
+ it("StaticTokenStore authenticates configured bearer tokens", () => {
7
+ const tokens = new StaticTokenStore([{username: "edge", token: "secret-token"}])
8
8
 
9
- const principal = tokens.authenticate("secret-token")
9
+ const principal = tokens.authenticate("secret-token")
10
10
 
11
- assert.equal(principal?.username, "edge")
12
- assert.equal(principal?.userId.startsWith("static:"), true)
13
- assert.equal(tokens.authenticate("wrong-token"), undefined)
11
+ expect(principal?.username).toBe("edge")
12
+ expect(principal?.userId.startsWith("static:")).toBe(true)
13
+ expect(tokens.authenticate("wrong-token")).toBeUndefined()
14
+ })
14
15
  })
@@ -1,8 +1,7 @@
1
- import assert from "node:assert/strict"
1
+ import {describe, expect, it} from "@velocious/testing"
2
2
  import {mkdtemp, rm} from "node:fs/promises"
3
3
  import {tmpdir} from "node:os"
4
4
  import {join} from "node:path"
5
- import test from "node:test"
6
5
 
7
6
  import {createSocketductConnectionFactory} from "../../../src/client/connect.js"
8
7
  import {SocketductSocket} from "../../../src/client/socketduct-socket.js"
@@ -14,45 +13,47 @@ async function createSpoolRoot() {
14
13
  return await mkdtemp(join(tmpdir(), "socketduct-connect-test-"))
15
14
  }
16
15
 
17
- test("createSocketductConnectionFactory merges base relay options with per-call targets", async () => {
18
- const spoolDirectory = await createSpoolRoot()
19
- const createConnection = createSocketductConnectionFactory({
20
- relay: {host: "relay.local", port: 3100, token: "token"},
21
- spoolDirectory,
22
- reconnect: {initialDelayMs: 25},
23
- sessionNamePrefix: "docker"
16
+ describe("createSocketductConnectionFactory", () => {
17
+ it("createSocketductConnectionFactory merges base relay options with per-call targets", async () => {
18
+ const spoolDirectory = await createSpoolRoot()
19
+ const createConnection = createSocketductConnectionFactory({
20
+ relay: {host: "relay.local", port: 3100, token: "token"},
21
+ spoolDirectory,
22
+ reconnect: {initialDelayMs: 25},
23
+ sessionNamePrefix: "docker"
24
+ })
25
+
26
+ const socket = createConnection({host: "docker-api", port: 2375})
27
+
28
+ expect(socket instanceof SocketductSocket).toBe(true)
29
+ expect(socket.remoteAddress).toBe("docker-api")
30
+ expect(socket.remotePort).toBe(2375)
31
+ expect(socket.connecting).toBe(true)
32
+ socket.destroy()
33
+ await rm(spoolDirectory, {recursive: true, force: true})
24
34
  })
25
35
 
26
- const socket = createConnection({host: "docker-api", port: 2375})
27
-
28
- assert.equal(socket instanceof SocketductSocket, true)
29
- assert.equal(socket.remoteAddress, "docker-api")
30
- assert.equal(socket.remotePort, 2375)
31
- assert.equal(socket.connecting, true)
32
- socket.destroy()
33
- await rm(spoolDirectory, {recursive: true, force: true})
34
- })
35
-
36
- test("createSocketductConnectionFactory accepts explicit target and connect listener", async () => {
37
- const spoolDirectory = await createSpoolRoot()
38
- const createConnection = createSocketductConnectionFactory({
39
- relay: {host: "relay.local", port: 3100, token: "token"},
40
- spoolDirectory,
41
- sessionNamePrefix: "explicit"
36
+ it("createSocketductConnectionFactory accepts explicit target and connect listener", async () => {
37
+ const spoolDirectory = await createSpoolRoot()
38
+ const createConnection = createSocketductConnectionFactory({
39
+ relay: {host: "relay.local", port: 3100, token: "token"},
40
+ spoolDirectory,
41
+ sessionNamePrefix: "explicit"
42
+ })
43
+ let listenerCalled = false
44
+
45
+ const socket = createConnection({
46
+ target: {host: "docker-socket-shim", port: 2375},
47
+ sessionName: "custom session"
48
+ }, () => {
49
+ listenerCalled = true
50
+ })
51
+
52
+ expect(socket.remoteAddress).toBe("docker-socket-shim")
53
+ expect(socket.remotePort).toBe(2375)
54
+ socket.emit("connect")
55
+ expect(listenerCalled).toBe(true)
56
+ socket.destroy()
57
+ await rm(spoolDirectory, {recursive: true, force: true})
42
58
  })
43
- let listenerCalled = false
44
-
45
- const socket = createConnection({
46
- target: {host: "docker-socket-shim", port: 2375},
47
- sessionName: "custom session"
48
- }, () => {
49
- listenerCalled = true
50
- })
51
-
52
- assert.equal(socket.remoteAddress, "docker-socket-shim")
53
- assert.equal(socket.remotePort, 2375)
54
- socket.emit("connect")
55
- assert.equal(listenerCalled, true)
56
- socket.destroy()
57
- await rm(spoolDirectory, {recursive: true, force: true})
58
59
  })
@@ -1,6 +1,5 @@
1
1
  /* eslint-disable jsdoc/escape-inline-tags, jsdoc/require-param-type, jsdoc/require-returns */
2
- import assert from "node:assert/strict"
3
- import test from "node:test"
2
+ import {describe, expect, it} from "@velocious/testing"
4
3
 
5
4
  import {DeferredVirtualSocket} from "../../../src/client/deferred-virtual-socket.js"
6
5
  import {VirtualSocket} from "../../../src/client/virtual-socket.js"
@@ -63,108 +62,110 @@ function memberConnection() {
63
62
  }
64
63
  }
65
64
 
66
- test("DeferredVirtualSocket write ignores non-function callback values in both parameter slots", async () => {
67
- const first = deferredSocket()
68
- // @ts-expect-error Deliberate non-function callback value; the socket must ignore it.
69
- assert.equal(first.socket.write("hello", null), true)
70
- const closed = new Promise((resolve) => first.socket.once("close", resolve))
71
- first.socket.destroy()
72
- await closed
73
- assert.equal(first.socket.destroyed, true)
74
-
75
- const second = deferredSocket()
76
- // @ts-expect-error Deliberate non-function callback value; the socket must ignore it.
77
- assert.equal(second.socket.write("hello", "utf8", null), true)
78
- const closedAgain = new Promise((resolve) => second.socket.once("close", resolve))
79
- assert.doesNotThrow(() => second.socket.destroy())
80
- await closedAgain
81
- assert.equal(second.socket.destroyed, true)
82
- })
65
+ describe("DeferredVirtualSocket", () => {
66
+ it("DeferredVirtualSocket write ignores non-function callback values in both parameter slots", async () => {
67
+ const first = deferredSocket()
68
+ // @ts-expect-error Deliberate non-function callback value; the socket must ignore it.
69
+ expect(first.socket.write("hello", null)).toBe(true)
70
+ const closed = new Promise((resolve) => first.socket.once("close", resolve))
71
+ first.socket.destroy()
72
+ await closed
73
+ expect(first.socket.destroyed).toBe(true)
83
74
 
84
- test("DeferredVirtualSocket write budget failure destroys the socket without crashing on non-function callback values", async () => {
85
- const failure = new Error("budget exhausted")
86
- const {socket} = deferredSocket({reserveMemoryError: failure})
87
- const error = new Promise((resolve) => socket.once("error", resolve))
88
- // @ts-expect-error Deliberate non-function callback value; the socket must ignore it.
89
- assert.equal(socket.write("hello", "utf8", null), false)
90
- assert.equal(await error, failure)
91
- assert.equal(socket.destroyed, true)
92
- })
75
+ const second = deferredSocket()
76
+ // @ts-expect-error Deliberate non-function callback value; the socket must ignore it.
77
+ expect(second.socket.write("hello", "utf8", null)).toBe(true)
78
+ const closedAgain = new Promise((resolve) => second.socket.once("close", resolve))
79
+ await expect(() => second.socket.destroy()).not.toThrow()
80
+ await closedAgain
81
+ expect(second.socket.destroyed).toBe(true)
82
+ })
93
83
 
94
- test("DeferredVirtualSocket write budget failure invokes the completion callback with the failure", async () => {
95
- const failure = new Error("budget exhausted")
96
- const {reservedBytes, socket} = deferredSocket({reserveMemoryError: failure})
97
- /** @type {Error | null | undefined} */
98
- let callbackError
99
- const error = new Promise((resolve) => socket.once("error", resolve))
100
- assert.equal(socket.write("hello", "utf8", (received) => {
101
- callbackError = received
102
- }), false)
103
- await error
104
- assert.equal(callbackError, failure)
105
- assert.equal(reservedBytes(), 0)
106
- assert.equal(socket.destroyed, true)
107
- })
84
+ it("DeferredVirtualSocket write budget failure destroys the socket without crashing on non-function callback values", async () => {
85
+ const failure = new Error("budget exhausted")
86
+ const {socket} = deferredSocket({reserveMemoryError: failure})
87
+ const error = new Promise((resolve) => socket.once("error", resolve))
88
+ // @ts-expect-error Deliberate non-function callback value; the socket must ignore it.
89
+ expect(socket.write("hello", "utf8", null)).toBe(false)
90
+ expect(await error).toBe(failure)
91
+ expect(socket.destroyed).toBe(true)
92
+ })
108
93
 
109
- test("DeferredVirtualSocket write completion after member assignment ignores non-function callback values", async () => {
110
- const {socket} = deferredSocket()
111
- const member = memberConnection()
112
- socket.assign(new VirtualSocket(member.connection, 1, {host: "127.0.0.1", port: 4793}), "gateway-1")
113
- // @ts-expect-error Deliberate non-function callback value; the socket must ignore it.
114
- assert.equal(socket.write("hello", "utf8", null), true)
115
- await new Promise((resolve) => setImmediate(resolve))
116
- const written = member.writes[0]
117
- assert(written !== undefined)
118
- assert.equal(written.toString(), "hello")
119
- })
94
+ it("DeferredVirtualSocket write budget failure invokes the completion callback with the failure", async () => {
95
+ const failure = new Error("budget exhausted")
96
+ const {reservedBytes, socket} = deferredSocket({reserveMemoryError: failure})
97
+ /** @type {Error | null | undefined} */
98
+ let callbackError
99
+ const error = new Promise((resolve) => socket.once("error", resolve))
100
+ expect(socket.write("hello", "utf8", (received) => {
101
+ callbackError = received
102
+ })).toBe(false)
103
+ await error
104
+ expect(callbackError).toBe(failure)
105
+ expect(reservedBytes()).toBe(0)
106
+ expect(socket.destroyed).toBe(true)
107
+ })
120
108
 
121
- test("DeferredVirtualSocket end ignores non-function callback values in both parameter slots", async () => {
122
- const first = deferredSocket()
123
- assert.doesNotThrow(() => {
109
+ it("DeferredVirtualSocket write completion after member assignment ignores non-function callback values", async () => {
110
+ const {socket} = deferredSocket()
111
+ const member = memberConnection()
112
+ socket.assign(new VirtualSocket(member.connection, 1, {host: "127.0.0.1", port: 4793}), "gateway-1")
124
113
  // @ts-expect-error Deliberate non-function callback value; the socket must ignore it.
125
- first.socket.end("hello", null)
114
+ expect(socket.write("hello", "utf8", null)).toBe(true)
115
+ await new Promise((resolve) => setImmediate(resolve))
116
+ const written = member.writes[0]
117
+ expect(written).toBeDefined()
118
+ expect(written?.toString()).toBe("hello")
126
119
  })
127
- assert.equal(first.socket.writableEnded, true)
128
- const closed = new Promise((resolve) => first.socket.once("close", resolve))
129
- first.socket.destroy()
130
- await closed
131
- assert.equal(first.socket.destroyed, true)
132
120
 
133
- const second = deferredSocket()
134
- assert.doesNotThrow(() => {
135
- // @ts-expect-error Deliberate non-function callback value; the socket must ignore it.
136
- second.socket.end("hello", "utf8", null)
121
+ it("DeferredVirtualSocket end ignores non-function callback values in both parameter slots", async () => {
122
+ const first = deferredSocket()
123
+ await expect(() => {
124
+ // @ts-expect-error Deliberate non-function callback value; the socket must ignore it.
125
+ first.socket.end("hello", null)
126
+ }).not.toThrow()
127
+ expect(first.socket.writableEnded).toBe(true)
128
+ const closed = new Promise((resolve) => first.socket.once("close", resolve))
129
+ first.socket.destroy()
130
+ await closed
131
+ expect(first.socket.destroyed).toBe(true)
132
+
133
+ const second = deferredSocket()
134
+ await expect(() => {
135
+ // @ts-expect-error Deliberate non-function callback value; the socket must ignore it.
136
+ second.socket.end("hello", "utf8", null)
137
+ }).not.toThrow()
138
+ expect(second.socket.writableEnded).toBe(true)
139
+ const closedAgain = new Promise((resolve) => second.socket.once("close", resolve))
140
+ second.socket.destroy()
141
+ await closedAgain
142
+ expect(second.socket.destroyed).toBe(true)
137
143
  })
138
- assert.equal(second.socket.writableEnded, true)
139
- const closedAgain = new Promise((resolve) => second.socket.once("close", resolve))
140
- second.socket.destroy()
141
- await closedAgain
142
- assert.equal(second.socket.destroyed, true)
143
- })
144
144
 
145
- test("DeferredVirtualSocket end does not forward non-function callback values to the stream", async () => {
146
- const {socket} = deferredSocket()
147
- assert.doesNotThrow(() => {
148
- // @ts-expect-error Deliberate non-function callback value; the socket must ignore it.
149
- socket.end("hello", "utf8", 42)
145
+ it("DeferredVirtualSocket end does not forward non-function callback values to the stream", async () => {
146
+ const {socket} = deferredSocket()
147
+ await expect(() => {
148
+ // @ts-expect-error Deliberate non-function callback value; the socket must ignore it.
149
+ socket.end("hello", "utf8", 42)
150
+ }).not.toThrow()
151
+ expect(socket.writableEnded).toBe(true)
152
+ const closed = new Promise((resolve) => socket.once("close", resolve))
153
+ socket.destroy()
154
+ await closed
150
155
  })
151
- assert.equal(socket.writableEnded, true)
152
- const closed = new Promise((resolve) => socket.once("close", resolve))
153
- socket.destroy()
154
- await closed
155
- })
156
156
 
157
- test("DeferredVirtualSocket end completion after member assignment invokes the callback on finish", async () => {
158
- const {socket} = deferredSocket()
159
- const member = memberConnection()
160
- socket.assign(new VirtualSocket(member.connection, 1, {host: "127.0.0.1", port: 4793}), "gateway-1")
161
- let finishedCallback = false
162
- const finished = new Promise((resolve) => socket.once("finish", resolve))
163
- socket.end("hello", "utf8", () => {
164
- finishedCallback = true
157
+ it("DeferredVirtualSocket end completion after member assignment invokes the callback on finish", async () => {
158
+ const {socket} = deferredSocket()
159
+ const member = memberConnection()
160
+ socket.assign(new VirtualSocket(member.connection, 1, {host: "127.0.0.1", port: 4793}), "gateway-1")
161
+ let finishedCallback = false
162
+ const finished = new Promise((resolve) => socket.once("finish", resolve))
163
+ socket.end("hello", "utf8", () => {
164
+ finishedCallback = true
165
+ })
166
+ await finished
167
+ expect(finishedCallback).toBe(true)
168
+ expect(socket.writableFinished).toBe(true)
169
+ expect(member.ended()).toBe(1)
165
170
  })
166
- await finished
167
- assert.equal(finishedCallback, true)
168
- assert.equal(socket.writableFinished, true)
169
- assert.equal(member.ended(), 1)
170
171
  })