socketduct 0.1.24 → 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.
- package/AGENTS.md +2 -2
- package/changelog.d/20261010130509-bound-log-frame-compression-memory.md +1 -0
- package/docs/runbooks/jsonl-integrity-rollout.md +8 -6
- package/package.json +2 -2
- package/scripts/test.js +9 -14
- package/src/client/multiplexed-transport-set.js +20 -2
- package/src/logging/compressed-log-writer.js +1 -1
- package/src/logging/zstd-frame-codec.js +15 -1
- package/src/reverse/client.js +44 -1
- package/src/reverse/hub.js +10 -1
- package/src/reverse/runtime.js +11 -2
- package/test/e2e/gateway-process.test.js +93 -92
- package/test/e2e/logger-shutdown-process.test.js +40 -37
- package/test/e2e/reverse-client-recovery-process.test.js +107 -103
- package/test/e2e/reverse-gateway-process.test.js +328 -318
- package/test/integration/initial-target-recovery.test.js +333 -334
- package/test/integration/logging-cli.test.js +121 -119
- package/test/integration/logging-compressed-recovery.test.js +96 -95
- package/test/integration/multiplexed-compatibility.test.js +178 -177
- package/test/integration/multiplexed-corrections.test.js +360 -359
- package/test/integration/multiplexed-failures.test.js +253 -251
- package/test/integration/multiplexed-transport-set.test.js +334 -322
- package/test/integration/multiplexed-transport.test.js +135 -134
- package/test/integration/reverse-gateway-stream-id-reset.test.js +90 -89
- package/test/integration/reverse-gateway.test.js +2309 -2218
- package/test/integration/reverse-resume-continuity.test.js +219 -218
- package/test/integration/reverse-route-class.test.js +363 -278
- package/test/integration/socketduct-http-agent.test.js +314 -312
- package/test/integration/socketduct-https-agent.test.js +516 -485
- package/test/integration/stream-compression.test.js +862 -824
- package/test/integration/tcp-forwarding.test.js +2550 -2447
- package/test/integration/tls-forwarding.test.js +593 -592
- package/test/unit/auth/auth.test.js +43 -44
- package/test/unit/auth/static-token-store.test.js +9 -8
- package/test/unit/client/connect.test.js +41 -40
- package/test/unit/client/deferred-virtual-socket.test.js +95 -94
- package/test/unit/client/multiplexed-spool-budget.test.js +155 -154
- package/test/unit/client/multiplexed-transport-set.test.js +845 -732
- package/test/unit/client/socketduct-socket.test.js +768 -771
- package/test/unit/client/virtual-socket.test.js +198 -168
- package/test/unit/config/docker-gateway-files.test.js +224 -224
- package/test/unit/config/environment.test.js +472 -492
- package/test/unit/index.test.js +53 -52
- package/test/unit/logging/compressed-log-worker.test.js +23 -22
- package/test/unit/logging/compressed-log-writer.test.js +155 -154
- package/test/unit/logging/log-retention.test.js +52 -47
- package/test/unit/logging/log-segment-index.test.js +84 -81
- package/test/unit/logging/zstd-frame-codec.test.js +28 -19
- package/test/unit/protocol/compression.test.js +254 -298
- package/test/unit/protocol/frame.test.js +280 -276
- package/test/unit/protocol/v3-compression.test.js +21 -23
- package/test/unit/protocol/v3-frame.test.js +42 -41
- package/test/unit/release-configuration.test.js +15 -12
- package/test/unit/reverse/disconnect-attribution.test.js +65 -64
- package/test/unit/reverse-terminal-outcomes.test.js +53 -50
- package/test/unit/server/control-handler.test.js +232 -231
- package/test/unit/server/primary.test.js +51 -49
- package/test/unit/server/relay-runtime.test.js +280 -294
- package/test/unit/sessions/session.test.js +48 -62
- package/test/unit/spool/spool-file.test.js +401 -385
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
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
|
-
|
|
15
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
34
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
28
|
+
const token = await login(users, tokens, {
|
|
29
|
+
username: "kasper",
|
|
30
|
+
password: "secret-password"
|
|
31
|
+
})
|
|
41
32
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
expect(tokens.authenticate(token.value)?.userId).toBe("user-1")
|
|
56
|
+
now = 1_501
|
|
57
|
+
expect(tokens.authenticate(token.value)).toBeUndefined()
|
|
60
58
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
|
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
|
-
|
|
7
|
-
|
|
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
|
-
|
|
9
|
+
const principal = tokens.authenticate("secret-token")
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
})
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
})
|