@bigmistqke/rpc 0.1.0
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/LICENSE +21 -0
- package/dist/fetch-node.d.ts +11 -0
- package/dist/fetch-node.js +342 -0
- package/dist/fetch-node.js.map +1 -0
- package/dist/fetch.d.ts +36 -0
- package/dist/fetch.js +369 -0
- package/dist/fetch.js.map +1 -0
- package/dist/messenger.d.ts +50 -0
- package/dist/messenger.js +540 -0
- package/dist/messenger.js.map +1 -0
- package/dist/stream.d.ts +46 -0
- package/dist/stream.js +601 -0
- package/dist/stream.js.map +1 -0
- package/dist/types-4d4495dd.d.ts +40 -0
- package/package.json +42 -0
- package/src/fetch/index.ts +84 -0
- package/src/fetch/node.ts +44 -0
- package/src/message-protocol.ts +57 -0
- package/src/messenger.ts +176 -0
- package/src/server-send-events/index.ts +129 -0
- package/src/stream/encoding.ts +362 -0
- package/src/stream/index.ts +162 -0
- package/src/types.ts +104 -0
- package/src/utils.ts +159 -0
- package/test/encoding.test.ts +413 -0
- package/test/fetch.test.ts +310 -0
- package/test/message-protocol.test.ts +166 -0
- package/test/messenger.test.ts +316 -0
- package/test/sse.test.ts +356 -0
- package/test/stream.test.ts +351 -0
- package/test/utils.test.ts +336 -0
- package/tsconfig.json +23 -0
- package/tsup.config.ts +17 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
2
|
+
import { expose, rpc, isFetchRequest, Payload } from '../src/fetch/index'
|
|
3
|
+
|
|
4
|
+
// Store original fetch
|
|
5
|
+
const originalFetch = globalThis.fetch
|
|
6
|
+
|
|
7
|
+
describe('isFetchRequest', () => {
|
|
8
|
+
it('should return true for requests with RPC header', () => {
|
|
9
|
+
const request = new Request('http://example.com', {
|
|
10
|
+
headers: { RPC_RR_PROXY: 'true' },
|
|
11
|
+
})
|
|
12
|
+
expect(isFetchRequest({ request })).toBe(true)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('should return false for requests without RPC header', () => {
|
|
16
|
+
const request = new Request('http://example.com')
|
|
17
|
+
expect(isFetchRequest({ request })).toBe(false)
|
|
18
|
+
})
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
describe('Payload', () => {
|
|
22
|
+
describe('validate', () => {
|
|
23
|
+
it('should validate correct payloads', () => {
|
|
24
|
+
expect(Payload.validate({ topics: ['method'], args: [] })).toBe(true)
|
|
25
|
+
expect(Payload.validate({ topics: ['a', 'b'], args: [1, 'test'] })).toBe(true)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('should reject invalid payloads', () => {
|
|
29
|
+
expect(Payload.validate({})).toBe(false)
|
|
30
|
+
expect(Payload.validate({ topics: 'not-array', args: [] })).toBe(false)
|
|
31
|
+
expect(Payload.validate({ topics: [], args: 'not-array' })).toBe(false)
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
describe('create', () => {
|
|
36
|
+
it('should create valid payloads', () => {
|
|
37
|
+
const payload = Payload.create(['test', 'method'], [1, 2])
|
|
38
|
+
expect(payload).toEqual({ topics: ['test', 'method'], args: [1, 2] })
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
describe('expose', () => {
|
|
44
|
+
it('should handle valid RPC requests', async () => {
|
|
45
|
+
const methods = {
|
|
46
|
+
greet: (name: string) => `Hello, ${name}!`,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const handler = expose(methods)
|
|
50
|
+
const request = new Request('http://example.com/greet', {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
body: JSON.stringify({ topics: ['greet'], args: ['World'] }),
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const response = await handler({ request })
|
|
56
|
+
|
|
57
|
+
expect(response.status).toBe(200)
|
|
58
|
+
const body = await response.json()
|
|
59
|
+
expect(body).toEqual({ payload: 'Hello, World!' })
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('should handle nested method calls', async () => {
|
|
63
|
+
const methods = {
|
|
64
|
+
math: {
|
|
65
|
+
add: (a: number, b: number) => a + b,
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const handler = expose(methods)
|
|
70
|
+
const request = new Request('http://example.com/math/add', {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
body: JSON.stringify({ topics: ['math', 'add'], args: [5, 3] }),
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
const response = await handler({ request })
|
|
76
|
+
|
|
77
|
+
expect(response.status).toBe(200)
|
|
78
|
+
const body = await response.json()
|
|
79
|
+
expect(body).toEqual({ payload: 8 })
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('should handle async methods', async () => {
|
|
83
|
+
const methods = {
|
|
84
|
+
asyncMethod: async () => {
|
|
85
|
+
await new Promise(resolve => setTimeout(resolve, 10))
|
|
86
|
+
return 'async result'
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const handler = expose(methods)
|
|
91
|
+
const request = new Request('http://example.com', {
|
|
92
|
+
method: 'POST',
|
|
93
|
+
body: JSON.stringify({ topics: ['asyncMethod'], args: [] }),
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
const response = await handler({ request })
|
|
97
|
+
|
|
98
|
+
expect(response.status).toBe(200)
|
|
99
|
+
const body = await response.json()
|
|
100
|
+
expect(body).toEqual({ payload: 'async result' })
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('should return 500 for invalid payload shape', async () => {
|
|
104
|
+
const methods = { test: () => 'ok' }
|
|
105
|
+
const handler = expose(methods)
|
|
106
|
+
|
|
107
|
+
const request = new Request('http://example.com', {
|
|
108
|
+
method: 'POST',
|
|
109
|
+
body: JSON.stringify({ invalid: 'data' }),
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
const response = await handler({ request })
|
|
113
|
+
|
|
114
|
+
expect(response.status).toBe(500)
|
|
115
|
+
expect(response.statusText).toBe('Incorrect shape')
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('should return 500 when method throws string error', async () => {
|
|
119
|
+
const methods = {
|
|
120
|
+
failing: () => {
|
|
121
|
+
throw 'String error message'
|
|
122
|
+
},
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const handler = expose(methods)
|
|
126
|
+
const request = new Request('http://example.com', {
|
|
127
|
+
method: 'POST',
|
|
128
|
+
body: JSON.stringify({ topics: ['failing'], args: [] }),
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
const response = await handler({ request })
|
|
132
|
+
|
|
133
|
+
expect(response.status).toBe(500)
|
|
134
|
+
expect(response.statusText).toBe('String error message')
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('should return 500 when method throws Error object', async () => {
|
|
138
|
+
const methods = {
|
|
139
|
+
failing: () => {
|
|
140
|
+
throw new Error('Error object message')
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const handler = expose(methods)
|
|
145
|
+
const request = new Request('http://example.com', {
|
|
146
|
+
method: 'POST',
|
|
147
|
+
body: JSON.stringify({ topics: ['failing'], args: [] }),
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
const response = await handler({ request })
|
|
151
|
+
|
|
152
|
+
expect(response.status).toBe(500)
|
|
153
|
+
expect(response.statusText).toBe('Error object message')
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('should return 500 with undefined statusText for non-string/non-Error throws', async () => {
|
|
157
|
+
const methods = {
|
|
158
|
+
failing: () => {
|
|
159
|
+
throw { custom: 'error' }
|
|
160
|
+
},
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const handler = expose(methods)
|
|
164
|
+
const request = new Request('http://example.com', {
|
|
165
|
+
method: 'POST',
|
|
166
|
+
body: JSON.stringify({ topics: ['failing'], args: [] }),
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
const response = await handler({ request })
|
|
170
|
+
|
|
171
|
+
expect(response.status).toBe(500)
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it('should return 500 when method does not exist', async () => {
|
|
175
|
+
const methods = {}
|
|
176
|
+
const handler = expose(methods)
|
|
177
|
+
|
|
178
|
+
const request = new Request('http://example.com', {
|
|
179
|
+
method: 'POST',
|
|
180
|
+
body: JSON.stringify({ topics: ['nonexistent'], args: [] }),
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
const response = await handler({ request })
|
|
184
|
+
|
|
185
|
+
expect(response.status).toBe(500)
|
|
186
|
+
})
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
describe('rpc', () => {
|
|
190
|
+
beforeEach(() => {
|
|
191
|
+
globalThis.fetch = vi.fn()
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
afterEach(() => {
|
|
195
|
+
globalThis.fetch = originalFetch
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it('should create proxy that makes POST requests', async () => {
|
|
199
|
+
vi.mocked(globalThis.fetch).mockResolvedValue(
|
|
200
|
+
new Response(JSON.stringify({ payload: 'result' }), { status: 200 }),
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
const proxy = rpc<{ method: () => string }>('http://api.example.com')
|
|
204
|
+
const result = await proxy.method()
|
|
205
|
+
|
|
206
|
+
expect(result).toBe('result')
|
|
207
|
+
expect(globalThis.fetch).toHaveBeenCalledWith(
|
|
208
|
+
expect.objectContaining({
|
|
209
|
+
method: 'POST',
|
|
210
|
+
}),
|
|
211
|
+
)
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
it('should construct URL from topics', async () => {
|
|
215
|
+
vi.mocked(globalThis.fetch).mockResolvedValue(
|
|
216
|
+
new Response(JSON.stringify({ payload: 'ok' }), { status: 200 }),
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
const proxy = rpc<{ user: { profile: { get: () => string } } }>('http://api.example.com')
|
|
220
|
+
await proxy.user.profile.get()
|
|
221
|
+
|
|
222
|
+
const calledRequest = vi.mocked(globalThis.fetch).mock.calls[0][0] as Request
|
|
223
|
+
expect(calledRequest.url).toBe('http://api.example.com/user/profile/get')
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
it('should include RPC header in requests', async () => {
|
|
227
|
+
vi.mocked(globalThis.fetch).mockResolvedValue(
|
|
228
|
+
new Response(JSON.stringify({ payload: 'ok' }), { status: 200 }),
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
const proxy = rpc<{ test: () => void }>('http://api.example.com')
|
|
232
|
+
await proxy.test()
|
|
233
|
+
|
|
234
|
+
const calledRequest = vi.mocked(globalThis.fetch).mock.calls[0][0] as Request
|
|
235
|
+
expect(calledRequest.headers.get('RPC_RR_PROXY')).toBe('true')
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it('should send topics and args in request body', async () => {
|
|
239
|
+
vi.mocked(globalThis.fetch).mockResolvedValue(
|
|
240
|
+
new Response(JSON.stringify({ payload: 'ok' }), { status: 200 }),
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
const proxy = rpc<{ add: (a: number, b: number) => number }>('http://api.example.com')
|
|
244
|
+
await proxy.add(1, 2)
|
|
245
|
+
|
|
246
|
+
const calledRequest = vi.mocked(globalThis.fetch).mock.calls[0][0] as Request
|
|
247
|
+
const body = await calledRequest.json()
|
|
248
|
+
expect(body).toEqual({ topics: ['add'], args: [1, 2] })
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
it('should throw when response status is not 200', async () => {
|
|
252
|
+
vi.mocked(globalThis.fetch).mockResolvedValue(
|
|
253
|
+
new Response(null, { status: 500, statusText: 'Internal Server Error' }),
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
const proxy = rpc<{ method: () => void }>('http://api.example.com')
|
|
257
|
+
|
|
258
|
+
await expect(proxy.method()).rejects.toBe('Internal Server Error')
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
// Note: The source code attempts to use GET when topics is empty, but still includes
|
|
262
|
+
// a body which causes an error. This test documents that behavior.
|
|
263
|
+
it('should throw when calling with empty topics due to GET with body', async () => {
|
|
264
|
+
vi.mocked(globalThis.fetch).mockResolvedValue(
|
|
265
|
+
new Response(JSON.stringify({ payload: 'ok' }), { status: 200 }),
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
const proxy = rpc<() => string>('http://api.example.com')
|
|
269
|
+
// Direct call with no topics - this throws because GET can't have a body
|
|
270
|
+
await expect((proxy as any)()).rejects.toThrow('Request with GET/HEAD method cannot have body')
|
|
271
|
+
})
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
describe('expose + rpc integration', () => {
|
|
275
|
+
beforeEach(() => {
|
|
276
|
+
// Create an in-memory mock that connects expose and rpc
|
|
277
|
+
const methods = {
|
|
278
|
+
greet: (name: string) => `Hello, ${name}!`,
|
|
279
|
+
math: {
|
|
280
|
+
add: (a: number, b: number) => a + b,
|
|
281
|
+
subtract: (a: number, b: number) => a - b,
|
|
282
|
+
},
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const handler = expose(methods)
|
|
286
|
+
|
|
287
|
+
globalThis.fetch = vi.fn(async (input: RequestInfo | URL) => {
|
|
288
|
+
const request = input as Request
|
|
289
|
+
return handler({ request })
|
|
290
|
+
})
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
afterEach(() => {
|
|
294
|
+
globalThis.fetch = originalFetch
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
it('should work end-to-end', async () => {
|
|
298
|
+
const proxy = rpc<{
|
|
299
|
+
greet: (name: string) => string
|
|
300
|
+
math: {
|
|
301
|
+
add: (a: number, b: number) => number
|
|
302
|
+
subtract: (a: number, b: number) => number
|
|
303
|
+
}
|
|
304
|
+
}>('http://test.local')
|
|
305
|
+
|
|
306
|
+
expect(await proxy.greet('Test')).toBe('Hello, Test!')
|
|
307
|
+
expect(await proxy.math.add(10, 5)).toBe(15)
|
|
308
|
+
expect(await proxy.math.subtract(10, 5)).toBe(5)
|
|
309
|
+
})
|
|
310
|
+
})
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
RequestShape,
|
|
4
|
+
ResponseShape,
|
|
5
|
+
ErrorShape,
|
|
6
|
+
RPCPayloadShape,
|
|
7
|
+
$MESSENGER_REQUEST,
|
|
8
|
+
$MESSENGER_RESPONSE,
|
|
9
|
+
$MESSENGER_ERROR,
|
|
10
|
+
$MESSENGER_RPC_REQUEST,
|
|
11
|
+
} from '../src/message-protocol'
|
|
12
|
+
|
|
13
|
+
describe('RequestShape', () => {
|
|
14
|
+
describe('validate', () => {
|
|
15
|
+
it('should validate correct request objects', () => {
|
|
16
|
+
expect(RequestShape.validate({ [$MESSENGER_REQUEST]: 0, payload: 'test' })).toBe(true)
|
|
17
|
+
expect(RequestShape.validate({ [$MESSENGER_REQUEST]: 123, payload: { data: true } })).toBe(
|
|
18
|
+
true,
|
|
19
|
+
)
|
|
20
|
+
expect(RequestShape.validate({ [$MESSENGER_REQUEST]: 999, payload: null })).toBe(true)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('should reject invalid request objects', () => {
|
|
24
|
+
expect(RequestShape.validate({})).toBe(false)
|
|
25
|
+
expect(RequestShape.validate({ [$MESSENGER_REQUEST]: 'not a number', payload: 'test' })).toBe(
|
|
26
|
+
false,
|
|
27
|
+
)
|
|
28
|
+
expect(RequestShape.validate({ payload: 'test' })).toBe(false)
|
|
29
|
+
expect(RequestShape.validate(null)).toBe(false)
|
|
30
|
+
expect(RequestShape.validate(undefined)).toBe(false)
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
describe('create', () => {
|
|
35
|
+
it('should create valid request objects', () => {
|
|
36
|
+
const request = RequestShape.create(42, { method: 'test' })
|
|
37
|
+
expect(request).toEqual({
|
|
38
|
+
[$MESSENGER_REQUEST]: 42,
|
|
39
|
+
payload: { method: 'test' },
|
|
40
|
+
})
|
|
41
|
+
expect(RequestShape.validate(request)).toBe(true)
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
describe('ResponseShape', () => {
|
|
47
|
+
describe('validate', () => {
|
|
48
|
+
it('should validate correct response objects', () => {
|
|
49
|
+
expect(ResponseShape.validate({ [$MESSENGER_RESPONSE]: 0, payload: 'result' })).toBe(true)
|
|
50
|
+
expect(ResponseShape.validate({ [$MESSENGER_RESPONSE]: 123, payload: { data: true } })).toBe(
|
|
51
|
+
true,
|
|
52
|
+
)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('should reject invalid response objects', () => {
|
|
56
|
+
expect(ResponseShape.validate({})).toBe(false)
|
|
57
|
+
expect(
|
|
58
|
+
ResponseShape.validate({ [$MESSENGER_RESPONSE]: 'not a number', payload: 'test' }),
|
|
59
|
+
).toBe(false)
|
|
60
|
+
expect(ResponseShape.validate({ payload: 'test' })).toBe(false)
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
describe('create', () => {
|
|
65
|
+
it('should create valid response objects from request', () => {
|
|
66
|
+
const request = { [$MESSENGER_REQUEST]: 42, payload: {} }
|
|
67
|
+
const response = ResponseShape.create(request, 'result')
|
|
68
|
+
expect(response).toEqual({
|
|
69
|
+
[$MESSENGER_RESPONSE]: 42,
|
|
70
|
+
payload: 'result',
|
|
71
|
+
})
|
|
72
|
+
expect(ResponseShape.validate(response)).toBe(true)
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
describe('ErrorShape', () => {
|
|
78
|
+
describe('validate', () => {
|
|
79
|
+
it('should validate correct error objects', () => {
|
|
80
|
+
expect(ErrorShape.validate({ [$MESSENGER_ERROR]: 0, error: 'error message' })).toBe(true)
|
|
81
|
+
expect(ErrorShape.validate({ [$MESSENGER_ERROR]: 123, error: { message: 'test' } })).toBe(
|
|
82
|
+
true,
|
|
83
|
+
)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('should reject invalid error objects', () => {
|
|
87
|
+
expect(ErrorShape.validate({})).toBe(false)
|
|
88
|
+
expect(ErrorShape.validate({ [$MESSENGER_ERROR]: 'not a number', error: 'test' })).toBe(false)
|
|
89
|
+
expect(ErrorShape.validate({ error: 'test' })).toBe(false)
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
describe('create', () => {
|
|
94
|
+
it('should create valid error objects from request', () => {
|
|
95
|
+
const request = { [$MESSENGER_REQUEST]: 42, payload: {} }
|
|
96
|
+
const error = ErrorShape.create(request, 'Something went wrong')
|
|
97
|
+
expect(error).toEqual({
|
|
98
|
+
[$MESSENGER_ERROR]: 42,
|
|
99
|
+
error: 'Something went wrong',
|
|
100
|
+
})
|
|
101
|
+
expect(ErrorShape.validate(error)).toBe(true)
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
describe('RPCPayloadShape', () => {
|
|
107
|
+
describe('validate', () => {
|
|
108
|
+
it('should validate correct RPC payload objects', () => {
|
|
109
|
+
expect(
|
|
110
|
+
RPCPayloadShape.validate({
|
|
111
|
+
[$MESSENGER_RPC_REQUEST]: true,
|
|
112
|
+
topics: ['method'],
|
|
113
|
+
args: [],
|
|
114
|
+
}),
|
|
115
|
+
).toBe(true)
|
|
116
|
+
expect(
|
|
117
|
+
RPCPayloadShape.validate({
|
|
118
|
+
[$MESSENGER_RPC_REQUEST]: true,
|
|
119
|
+
topics: ['user', 'profile', 'get'],
|
|
120
|
+
args: [1, 'test'],
|
|
121
|
+
}),
|
|
122
|
+
).toBe(true)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('should reject invalid RPC payload objects', () => {
|
|
126
|
+
expect(RPCPayloadShape.validate({})).toBe(false)
|
|
127
|
+
expect(RPCPayloadShape.validate({ [$MESSENGER_RPC_REQUEST]: true })).toBe(false)
|
|
128
|
+
expect(
|
|
129
|
+
RPCPayloadShape.validate({
|
|
130
|
+
[$MESSENGER_RPC_REQUEST]: true,
|
|
131
|
+
topics: 'not an array',
|
|
132
|
+
args: [],
|
|
133
|
+
}),
|
|
134
|
+
).toBe(false)
|
|
135
|
+
expect(
|
|
136
|
+
RPCPayloadShape.validate({
|
|
137
|
+
[$MESSENGER_RPC_REQUEST]: 'not a boolean',
|
|
138
|
+
topics: [],
|
|
139
|
+
args: [],
|
|
140
|
+
}),
|
|
141
|
+
).toBe(false)
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
describe('create', () => {
|
|
146
|
+
it('should create valid RPC payload objects', () => {
|
|
147
|
+
const payload = RPCPayloadShape.create(['user', 'get'], [42])
|
|
148
|
+
expect(payload).toEqual({
|
|
149
|
+
[$MESSENGER_RPC_REQUEST]: true,
|
|
150
|
+
topics: ['user', 'get'],
|
|
151
|
+
args: [42],
|
|
152
|
+
})
|
|
153
|
+
expect(RPCPayloadShape.validate(payload)).toBe(true)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('should handle empty topics and args', () => {
|
|
157
|
+
const payload = RPCPayloadShape.create([], [])
|
|
158
|
+
expect(payload).toEqual({
|
|
159
|
+
[$MESSENGER_RPC_REQUEST]: true,
|
|
160
|
+
topics: [],
|
|
161
|
+
args: [],
|
|
162
|
+
})
|
|
163
|
+
expect(RPCPayloadShape.validate(payload)).toBe(true)
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
})
|