odac 1.4.15 → 1.4.17
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/CHANGELOG.md +29 -0
- package/client/odac.js +38 -1
- package/docs/ai/skills/backend/forms.md +54 -3
- package/docs/ai/skills/backend/validation.md +30 -2
- package/docs/backend/05-forms/01-custom-forms.md +52 -0
- package/docs/backend/09-validation/01-the-validator-service.md +40 -0
- package/docs/backend/13-utilities/02-ipc.md +26 -1
- package/docs/frontend/03-forms/01-form-handling.md +30 -13
- package/package.json +2 -1
- package/src/Auth.js +252 -24
- package/src/Config.js +5 -1
- package/src/Ipc.js +87 -14
- package/src/Odac.js +23 -4
- package/src/Request.js +235 -34
- package/src/Route/Internal.js +32 -3
- package/src/Validator.js +143 -2
- package/src/View/Form.js +56 -0
- package/src/View/Image.js +15 -6
- package/src/View.js +2 -2
- package/src/WebSocket.js +20 -1
- package/test/Auth/check.test.js +219 -18
- package/test/Auth/verifyMagicLink.test.js +8 -1
- package/test/Ipc/subscribe.test.js +154 -0
- package/test/Ipc/subscribeRedis.test.js +141 -0
- package/test/Odac/cache.test.js +5 -2
- package/test/Odac/image.test.js +8 -5
- package/test/Odac/instance.test.js +5 -2
- package/test/Request/cache.test.js +1 -0
- package/test/Request/multipart.test.js +164 -0
- package/test/Validator/file.test.js +172 -0
- package/test/View/Form/generateFieldHtml.test.js +56 -0
- package/test/View/Image/serve.test.js +9 -4
- package/test/View/Image/url.test.js +10 -4
- package/test/View/addNavigateAttribute.test.js +10 -1
- package/test/View/print.test.js +10 -1
- package/test/WebSocket/Client/fragmentation.test.js +24 -5
- package/test/WebSocket/Client/limits.test.js +21 -2
- package/test/WebSocket/Client/readyState.test.js +29 -10
- package/test/WebSocket/Client/send.test.js +148 -0
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
const fs = require('fs')
|
|
2
2
|
const fsPromises = fs.promises
|
|
3
|
+
const os = require('os')
|
|
3
4
|
const path = require('path')
|
|
4
5
|
const Image = require('../../../src/View/Image')
|
|
5
6
|
|
|
6
|
-
const IMG_CACHE_DIR = './storage/.cache/img'
|
|
7
|
-
|
|
8
7
|
describe('Image.serve()', () => {
|
|
9
8
|
const testFilename = 'testserve1234567.webp'
|
|
10
|
-
|
|
9
|
+
let tmpDir, IMG_CACHE_DIR, testFilePath
|
|
11
10
|
|
|
12
11
|
beforeAll(async () => {
|
|
12
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'odac-image-serve-'))
|
|
13
|
+
global.__dir = tmpDir
|
|
14
|
+
IMG_CACHE_DIR = path.join(tmpDir, 'storage/.cache/img')
|
|
15
|
+
testFilePath = path.join(IMG_CACHE_DIR, testFilename)
|
|
16
|
+
|
|
13
17
|
await fsPromises.mkdir(IMG_CACHE_DIR, {recursive: true})
|
|
14
18
|
await fsPromises.writeFile(testFilePath, Buffer.from('fake-image-data'))
|
|
15
19
|
})
|
|
16
20
|
|
|
17
21
|
afterAll(async () => {
|
|
18
|
-
|
|
22
|
+
delete global.__dir
|
|
23
|
+
await fsPromises.rm(tmpDir, {recursive: true, force: true})
|
|
19
24
|
})
|
|
20
25
|
|
|
21
26
|
test('should return stream, type, and size for a cached file', async () => {
|
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
const fs = require('fs')
|
|
2
2
|
const fsPromises = fs.promises
|
|
3
|
+
const os = require('os')
|
|
3
4
|
const path = require('path')
|
|
4
5
|
const Image = require('../../../src/View/Image')
|
|
5
6
|
|
|
6
7
|
describe('Image.url()', () => {
|
|
7
|
-
|
|
8
|
-
const testImageDir = path.join(publicDir, 'images')
|
|
9
|
-
const testImagePath = path.join(testImageDir, 'url-test.jpg')
|
|
8
|
+
let tmpDir, testImagePath
|
|
10
9
|
|
|
11
10
|
beforeAll(async () => {
|
|
11
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'odac-image-url-'))
|
|
12
|
+
global.__dir = tmpDir
|
|
13
|
+
|
|
14
|
+
const testImageDir = path.join(tmpDir, 'public', 'images')
|
|
15
|
+
testImagePath = path.join(testImageDir, 'url-test.jpg')
|
|
16
|
+
|
|
12
17
|
await fsPromises.mkdir(testImageDir, {recursive: true})
|
|
13
18
|
await fsPromises.writeFile(testImagePath, Buffer.from('fake-jpg-data'))
|
|
14
19
|
})
|
|
15
20
|
|
|
16
21
|
afterAll(async () => {
|
|
17
|
-
|
|
22
|
+
delete global.__dir
|
|
23
|
+
await fsPromises.rm(tmpDir, {recursive: true, force: true})
|
|
18
24
|
})
|
|
19
25
|
|
|
20
26
|
test('should return empty string for empty src', async () => {
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
const fs = require('fs').promises
|
|
2
|
+
const fsSync = require('fs')
|
|
3
|
+
const os = require('os')
|
|
2
4
|
const path = require('path')
|
|
3
5
|
const View = require('../../src/View')
|
|
4
6
|
|
|
5
7
|
describe('View.#addNavigateAttribute()', () => {
|
|
6
8
|
let view
|
|
7
9
|
let mockOdac
|
|
10
|
+
let tmpDir
|
|
11
|
+
let originalCwd
|
|
8
12
|
|
|
9
13
|
beforeEach(() => {
|
|
10
|
-
|
|
14
|
+
tmpDir = fsSync.mkdtempSync(path.join(os.tmpdir(), 'odac-view-navattr-'))
|
|
15
|
+
global.__dir = tmpDir
|
|
16
|
+
originalCwd = process.cwd()
|
|
17
|
+
process.chdir(tmpDir)
|
|
11
18
|
mockOdac = {
|
|
12
19
|
Config: {view: {earlyHints: {enabled: false}}},
|
|
13
20
|
View: {},
|
|
@@ -28,6 +35,8 @@ describe('View.#addNavigateAttribute()', () => {
|
|
|
28
35
|
afterEach(() => {
|
|
29
36
|
jest.restoreAllMocks()
|
|
30
37
|
delete global.__dir
|
|
38
|
+
process.chdir(originalCwd)
|
|
39
|
+
fsSync.rmSync(tmpDir, {recursive: true, force: true})
|
|
31
40
|
})
|
|
32
41
|
|
|
33
42
|
it('should not wrap placeholder in display:contents if it is the first child of an element', async () => {
|
package/test/View/print.test.js
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
const fs = require('fs').promises
|
|
2
|
+
const fsSync = require('fs')
|
|
3
|
+
const os = require('os')
|
|
2
4
|
const path = require('path')
|
|
3
5
|
const View = require('../../src/View')
|
|
4
6
|
|
|
5
7
|
describe('View.print()', () => {
|
|
6
8
|
let view
|
|
7
9
|
let mockOdac
|
|
10
|
+
let tmpDir
|
|
11
|
+
let originalCwd
|
|
8
12
|
|
|
9
13
|
beforeEach(() => {
|
|
10
|
-
|
|
14
|
+
tmpDir = fsSync.mkdtempSync(path.join(os.tmpdir(), 'odac-view-print-'))
|
|
15
|
+
global.__dir = tmpDir
|
|
16
|
+
originalCwd = process.cwd()
|
|
17
|
+
process.chdir(tmpDir)
|
|
11
18
|
mockOdac = {
|
|
12
19
|
Config: {view: {earlyHints: {enabled: false}}},
|
|
13
20
|
View: {},
|
|
@@ -28,6 +35,8 @@ describe('View.print()', () => {
|
|
|
28
35
|
afterEach(() => {
|
|
29
36
|
jest.restoreAllMocks()
|
|
30
37
|
delete global.__dir
|
|
38
|
+
process.chdir(originalCwd)
|
|
39
|
+
fsSync.rmSync(tmpDir, {recursive: true, force: true})
|
|
31
40
|
})
|
|
32
41
|
|
|
33
42
|
it('should be a function', () => {
|
|
@@ -31,6 +31,25 @@ function createMockSocket() {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
const openClients = []
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Creates a client and registers it for teardown.
|
|
38
|
+
*
|
|
39
|
+
* The constructor starts a rate-limit interval that is only cleared on close(),
|
|
40
|
+
* so a client left open keeps Jest's event loop alive after the run finishes.
|
|
41
|
+
*/
|
|
42
|
+
function createClient(...args) {
|
|
43
|
+
const client = new WebSocketClient(...args)
|
|
44
|
+
openClients.push(client)
|
|
45
|
+
return client
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
afterEach(() => {
|
|
49
|
+
for (const client of openClients) client.close()
|
|
50
|
+
openClients.length = 0
|
|
51
|
+
})
|
|
52
|
+
|
|
34
53
|
describe('WebSocketClient Fragmentation', () => {
|
|
35
54
|
let server
|
|
36
55
|
|
|
@@ -40,7 +59,7 @@ describe('WebSocketClient Fragmentation', () => {
|
|
|
40
59
|
|
|
41
60
|
it('should reassemble fragmented text messages', () => {
|
|
42
61
|
const socket = createMockSocket()
|
|
43
|
-
const client =
|
|
62
|
+
const client = createClient(socket, server, 'frag-1')
|
|
44
63
|
client.resume()
|
|
45
64
|
|
|
46
65
|
const messages = []
|
|
@@ -60,7 +79,7 @@ describe('WebSocketClient Fragmentation', () => {
|
|
|
60
79
|
|
|
61
80
|
it('should reassemble fragmented binary messages', () => {
|
|
62
81
|
const socket = createMockSocket()
|
|
63
|
-
const client =
|
|
82
|
+
const client = createClient(socket, server, 'frag-2')
|
|
64
83
|
client.resume()
|
|
65
84
|
|
|
66
85
|
const messages = []
|
|
@@ -83,7 +102,7 @@ describe('WebSocketClient Fragmentation', () => {
|
|
|
83
102
|
|
|
84
103
|
it('should close with 1002 on unexpected continuation frame', () => {
|
|
85
104
|
const socket = createMockSocket()
|
|
86
|
-
const client =
|
|
105
|
+
const client = createClient(socket, server, 'frag-3')
|
|
87
106
|
client.resume()
|
|
88
107
|
|
|
89
108
|
const dataHandler = socket.on.mock.calls.find(c => c[0] === 'data')[1]
|
|
@@ -96,7 +115,7 @@ describe('WebSocketClient Fragmentation', () => {
|
|
|
96
115
|
|
|
97
116
|
it('should handle single unfragmented message normally', () => {
|
|
98
117
|
const socket = createMockSocket()
|
|
99
|
-
const client =
|
|
118
|
+
const client = createClient(socket, server, 'frag-4')
|
|
100
119
|
client.resume()
|
|
101
120
|
|
|
102
121
|
const messages = []
|
|
@@ -112,7 +131,7 @@ describe('WebSocketClient Fragmentation', () => {
|
|
|
112
131
|
|
|
113
132
|
it('should discard fragment buffer on close', () => {
|
|
114
133
|
const socket = createMockSocket()
|
|
115
|
-
const client =
|
|
134
|
+
const client = createClient(socket, server, 'frag-5')
|
|
116
135
|
client.resume()
|
|
117
136
|
|
|
118
137
|
const messages = []
|
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
const {WebSocketServer, WebSocketClient} = require('../../../src/WebSocket.js')
|
|
2
2
|
|
|
3
|
+
const openClients = []
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates a client and registers it for teardown.
|
|
7
|
+
*
|
|
8
|
+
* The constructor starts a rate-limit interval that is only cleared on close(),
|
|
9
|
+
* so a client left open keeps Jest's event loop alive after the run finishes.
|
|
10
|
+
*/
|
|
11
|
+
function createClient(...args) {
|
|
12
|
+
const client = new WebSocketClient(...args)
|
|
13
|
+
openClients.push(client)
|
|
14
|
+
return client
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
for (const client of openClients) client.close()
|
|
19
|
+
openClients.length = 0
|
|
20
|
+
})
|
|
21
|
+
|
|
3
22
|
describe('WebSocketClient Limits', () => {
|
|
4
23
|
let server
|
|
5
24
|
|
|
@@ -18,7 +37,7 @@ describe('WebSocketClient Limits', () => {
|
|
|
18
37
|
removeAllListeners: jest.fn(),
|
|
19
38
|
writable: true
|
|
20
39
|
}
|
|
21
|
-
const client =
|
|
40
|
+
const client = createClient(socket, server, 'test-id', {maxPayload: 10})
|
|
22
41
|
client.resume()
|
|
23
42
|
|
|
24
43
|
const buffer = Buffer.alloc(100)
|
|
@@ -42,7 +61,7 @@ describe('WebSocketClient Limits', () => {
|
|
|
42
61
|
removeAllListeners: jest.fn(),
|
|
43
62
|
writable: true
|
|
44
63
|
}
|
|
45
|
-
const client =
|
|
64
|
+
const client = createClient(socket, server, 'test-id', {rateLimit: {max: 2, window: 1000}})
|
|
46
65
|
client.resume()
|
|
47
66
|
|
|
48
67
|
const buffer = Buffer.alloc(7)
|
|
@@ -16,6 +16,25 @@ function createMockSocket() {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
const openClients = []
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates a client and registers it for teardown.
|
|
23
|
+
*
|
|
24
|
+
* The constructor starts a rate-limit interval that is only cleared on close(),
|
|
25
|
+
* so a client left open keeps Jest's event loop alive after the run finishes.
|
|
26
|
+
*/
|
|
27
|
+
function createClient(...args) {
|
|
28
|
+
const client = new WebSocketClient(...args)
|
|
29
|
+
openClients.push(client)
|
|
30
|
+
return client
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
for (const client of openClients) client.close()
|
|
35
|
+
openClients.length = 0
|
|
36
|
+
})
|
|
37
|
+
|
|
19
38
|
describe('WebSocketClient readyState', () => {
|
|
20
39
|
let server
|
|
21
40
|
|
|
@@ -41,13 +60,13 @@ describe('WebSocketClient readyState', () => {
|
|
|
41
60
|
|
|
42
61
|
it('should start in CONNECTING state', () => {
|
|
43
62
|
const socket = createMockSocket()
|
|
44
|
-
const client =
|
|
63
|
+
const client = createClient(socket, server, 'rs-1')
|
|
45
64
|
expect(client.readyState).toBe(READY_STATE.CONNECTING)
|
|
46
65
|
})
|
|
47
66
|
|
|
48
67
|
it('should transition to OPEN on resume()', () => {
|
|
49
68
|
const socket = createMockSocket()
|
|
50
|
-
const client =
|
|
69
|
+
const client = createClient(socket, server, 'rs-2')
|
|
51
70
|
|
|
52
71
|
client.resume()
|
|
53
72
|
|
|
@@ -57,7 +76,7 @@ describe('WebSocketClient readyState', () => {
|
|
|
57
76
|
|
|
58
77
|
it('should transition to CLOSED after close()', () => {
|
|
59
78
|
const socket = createMockSocket()
|
|
60
|
-
const client =
|
|
79
|
+
const client = createClient(socket, server, 'rs-3')
|
|
61
80
|
client.resume()
|
|
62
81
|
|
|
63
82
|
client.close()
|
|
@@ -68,7 +87,7 @@ describe('WebSocketClient readyState', () => {
|
|
|
68
87
|
|
|
69
88
|
it('should be idempotent — second close() is a no-op', () => {
|
|
70
89
|
const socket = createMockSocket()
|
|
71
|
-
const client =
|
|
90
|
+
const client = createClient(socket, server, 'rs-4')
|
|
72
91
|
client.resume()
|
|
73
92
|
|
|
74
93
|
client.close()
|
|
@@ -79,7 +98,7 @@ describe('WebSocketClient readyState', () => {
|
|
|
79
98
|
|
|
80
99
|
it('should not send data when in CONNECTING state', () => {
|
|
81
100
|
const socket = createMockSocket()
|
|
82
|
-
const client =
|
|
101
|
+
const client = createClient(socket, server, 'rs-5')
|
|
83
102
|
|
|
84
103
|
client.send('hello')
|
|
85
104
|
|
|
@@ -88,7 +107,7 @@ describe('WebSocketClient readyState', () => {
|
|
|
88
107
|
|
|
89
108
|
it('should not send data when in CLOSED state', () => {
|
|
90
109
|
const socket = createMockSocket()
|
|
91
|
-
const client =
|
|
110
|
+
const client = createClient(socket, server, 'rs-6')
|
|
92
111
|
client.resume()
|
|
93
112
|
client.close()
|
|
94
113
|
|
|
@@ -103,7 +122,7 @@ describe('WebSocketClient readyState', () => {
|
|
|
103
122
|
|
|
104
123
|
it('should not send ping when not OPEN', () => {
|
|
105
124
|
const socket = createMockSocket()
|
|
106
|
-
const client =
|
|
125
|
+
const client = createClient(socket, server, 'rs-7')
|
|
107
126
|
|
|
108
127
|
client.ping()
|
|
109
128
|
|
|
@@ -113,7 +132,7 @@ describe('WebSocketClient readyState', () => {
|
|
|
113
132
|
it('should not write when socket is not writable', () => {
|
|
114
133
|
const socket = createMockSocket()
|
|
115
134
|
socket.writable = false
|
|
116
|
-
const client =
|
|
135
|
+
const client = createClient(socket, server, 'rs-8')
|
|
117
136
|
client.resume()
|
|
118
137
|
|
|
119
138
|
client.send('hello')
|
|
@@ -123,7 +142,7 @@ describe('WebSocketClient readyState', () => {
|
|
|
123
142
|
|
|
124
143
|
it('should transition to CLOSED when socket fires close event', () => {
|
|
125
144
|
const socket = createMockSocket()
|
|
126
|
-
const client =
|
|
145
|
+
const client = createClient(socket, server, 'rs-9')
|
|
127
146
|
server.clients.set('rs-9', client)
|
|
128
147
|
client.resume()
|
|
129
148
|
|
|
@@ -136,7 +155,7 @@ describe('WebSocketClient readyState', () => {
|
|
|
136
155
|
|
|
137
156
|
it('should emit close event only once on double cleanup', () => {
|
|
138
157
|
const socket = createMockSocket()
|
|
139
|
-
const client =
|
|
158
|
+
const client = createClient(socket, server, 'rs-10')
|
|
140
159
|
server.clients.set('rs-10', client)
|
|
141
160
|
client.resume()
|
|
142
161
|
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
const {WebSocketServer, WebSocketClient} = require('../../../src/WebSocket.js')
|
|
2
|
+
|
|
3
|
+
function createMockSocket() {
|
|
4
|
+
return {
|
|
5
|
+
pause: jest.fn(),
|
|
6
|
+
resume: jest.fn(),
|
|
7
|
+
on: jest.fn(),
|
|
8
|
+
write: jest.fn(),
|
|
9
|
+
end: jest.fn(),
|
|
10
|
+
removeAllListeners: jest.fn(),
|
|
11
|
+
writable: true
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Splits the frame handed to socket.write() back into opcode and payload.
|
|
17
|
+
* Server-sent frames are never masked, so the payload starts right after
|
|
18
|
+
* the (possibly extended) length field.
|
|
19
|
+
*/
|
|
20
|
+
function readFrame(socket) {
|
|
21
|
+
const frame = socket.write.mock.calls[0][0]
|
|
22
|
+
const opcode = frame[0] & 0x0f
|
|
23
|
+
const length = frame[1] & 0x7f
|
|
24
|
+
|
|
25
|
+
let offset = 2
|
|
26
|
+
if (length === 126) offset = 4
|
|
27
|
+
else if (length === 127) offset = 10
|
|
28
|
+
|
|
29
|
+
return {opcode, payload: frame.subarray(offset)}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const openClients = []
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Creates a client and registers it for teardown.
|
|
36
|
+
*
|
|
37
|
+
* The constructor starts a rate-limit interval that is only cleared on close(),
|
|
38
|
+
* so a client left open keeps Jest's event loop alive after the run finishes.
|
|
39
|
+
*/
|
|
40
|
+
function createClient(...args) {
|
|
41
|
+
const client = new WebSocketClient(...args)
|
|
42
|
+
openClients.push(client)
|
|
43
|
+
return client
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
for (const client of openClients) client.close()
|
|
48
|
+
openClients.length = 0
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
describe('WebSocketClient send', () => {
|
|
52
|
+
let server
|
|
53
|
+
let client
|
|
54
|
+
let socket
|
|
55
|
+
|
|
56
|
+
beforeEach(() => {
|
|
57
|
+
server = new WebSocketServer()
|
|
58
|
+
socket = createMockSocket()
|
|
59
|
+
client = createClient(socket, server, 'send-1')
|
|
60
|
+
client.resume()
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('should send strings as TEXT frames', () => {
|
|
64
|
+
client.send('hello')
|
|
65
|
+
|
|
66
|
+
const {opcode, payload} = readFrame(socket)
|
|
67
|
+
|
|
68
|
+
expect(opcode).toBe(0x1)
|
|
69
|
+
expect(payload.toString('utf8')).toBe('hello')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('should send plain objects as JSON TEXT frames', () => {
|
|
73
|
+
client.send({type: 'ping', n: 1})
|
|
74
|
+
|
|
75
|
+
const {opcode, payload} = readFrame(socket)
|
|
76
|
+
|
|
77
|
+
expect(opcode).toBe(0x1)
|
|
78
|
+
expect(JSON.parse(payload.toString('utf8'))).toEqual({type: 'ping', n: 1})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('should send a Buffer as a BINARY frame instead of JSON', () => {
|
|
82
|
+
client.send(Buffer.from([0xde, 0xad, 0xbe, 0xef]))
|
|
83
|
+
|
|
84
|
+
const {opcode, payload} = readFrame(socket)
|
|
85
|
+
|
|
86
|
+
expect(opcode).toBe(0x2)
|
|
87
|
+
expect([...payload]).toEqual([0xde, 0xad, 0xbe, 0xef])
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('should send a TypedArray as a BINARY frame', () => {
|
|
91
|
+
client.send(new Uint8Array([1, 2, 3]))
|
|
92
|
+
|
|
93
|
+
const {opcode, payload} = readFrame(socket)
|
|
94
|
+
|
|
95
|
+
expect(opcode).toBe(0x2)
|
|
96
|
+
expect([...payload]).toEqual([1, 2, 3])
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('should send an ArrayBuffer as a BINARY frame', () => {
|
|
100
|
+
const view = new Uint8Array([9, 8, 7])
|
|
101
|
+
client.send(view.buffer)
|
|
102
|
+
|
|
103
|
+
const {opcode, payload} = readFrame(socket)
|
|
104
|
+
|
|
105
|
+
expect(opcode).toBe(0x2)
|
|
106
|
+
expect([...payload]).toEqual([9, 8, 7])
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('should preserve the bytes of multi-byte TypedArrays', () => {
|
|
110
|
+
// Buffer.from(view) would read each element as a number and truncate it
|
|
111
|
+
// to one byte, silently corrupting anything wider than a Uint8Array.
|
|
112
|
+
client.send(new Uint16Array([0x0100, 0x0200]))
|
|
113
|
+
|
|
114
|
+
const {opcode, payload} = readFrame(socket)
|
|
115
|
+
|
|
116
|
+
expect(opcode).toBe(0x2)
|
|
117
|
+
expect(payload.length).toBe(4)
|
|
118
|
+
expect([...payload]).toEqual([0x00, 0x01, 0x00, 0x02])
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('should honour the byteOffset of a view over a larger buffer', () => {
|
|
122
|
+
const backing = new Uint8Array([1, 2, 3, 4, 5, 6])
|
|
123
|
+
const view = backing.subarray(2, 5)
|
|
124
|
+
|
|
125
|
+
client.send(view)
|
|
126
|
+
|
|
127
|
+
const {payload} = readFrame(socket)
|
|
128
|
+
|
|
129
|
+
expect([...payload]).toEqual([3, 4, 5])
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('should send binary through sendBinary without corrupting wide views', () => {
|
|
133
|
+
client.sendBinary(new Uint16Array([0x0100]))
|
|
134
|
+
|
|
135
|
+
const {opcode, payload} = readFrame(socket)
|
|
136
|
+
|
|
137
|
+
expect(opcode).toBe(0x2)
|
|
138
|
+
expect([...payload]).toEqual([0x00, 0x01])
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('should not send when the connection is not open', () => {
|
|
142
|
+
const closed = createClient(createMockSocket(), server, 'send-2')
|
|
143
|
+
|
|
144
|
+
closed.send('hello')
|
|
145
|
+
|
|
146
|
+
expect(closed.readyState).toBe(WebSocketClient.CONNECTING)
|
|
147
|
+
})
|
|
148
|
+
})
|