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 {
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
  })