@atproto/lexicon 0.7.3 → 0.7.4

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 (54) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/lexicons.d.ts +1 -1
  3. package/dist/lexicons.d.ts.map +1 -1
  4. package/dist/lexicons.js.map +1 -1
  5. package/dist/serialize.d.ts +1 -1
  6. package/dist/serialize.d.ts.map +1 -1
  7. package/dist/serialize.js +8 -7
  8. package/dist/serialize.js.map +1 -1
  9. package/dist/types.d.ts +1 -1
  10. package/dist/types.d.ts.map +1 -1
  11. package/dist/types.js.map +1 -1
  12. package/dist/validation.d.ts +1 -1
  13. package/dist/validation.d.ts.map +1 -1
  14. package/dist/validation.js +1 -0
  15. package/dist/validation.js.map +1 -1
  16. package/dist/validators/blob.d.ts +1 -1
  17. package/dist/validators/blob.d.ts.map +1 -1
  18. package/dist/validators/blob.js +1 -0
  19. package/dist/validators/blob.js.map +1 -1
  20. package/dist/validators/complex.d.ts +1 -1
  21. package/dist/validators/complex.d.ts.map +1 -1
  22. package/dist/validators/complex.js +2 -1
  23. package/dist/validators/complex.js.map +1 -1
  24. package/dist/validators/formats.d.ts +1 -1
  25. package/dist/validators/formats.d.ts.map +1 -1
  26. package/dist/validators/formats.js.map +1 -1
  27. package/dist/validators/primitives.d.ts +1 -1
  28. package/dist/validators/primitives.d.ts.map +1 -1
  29. package/dist/validators/primitives.js +2 -1
  30. package/dist/validators/primitives.js.map +1 -1
  31. package/dist/validators/xrpc.d.ts +1 -1
  32. package/dist/validators/xrpc.d.ts.map +1 -1
  33. package/dist/validators/xrpc.js +3 -2
  34. package/dist/validators/xrpc.js.map +1 -1
  35. package/package.json +18 -13
  36. package/jest.config.cjs +0 -21
  37. package/src/blob-refs.ts +0 -65
  38. package/src/index.ts +0 -4
  39. package/src/lexicons.ts +0 -253
  40. package/src/serialize.ts +0 -102
  41. package/src/types.ts +0 -483
  42. package/src/util.ts +0 -58
  43. package/src/validation.ts +0 -84
  44. package/src/validators/blob.ts +0 -19
  45. package/src/validators/complex.ts +0 -212
  46. package/src/validators/formats.ts +0 -72
  47. package/src/validators/primitives.ts +0 -416
  48. package/src/validators/xrpc.ts +0 -53
  49. package/tests/_scaffolds/lexicons.ts +0 -545
  50. package/tests/general.test.ts +0 -1243
  51. package/tsconfig.build.json +0 -8
  52. package/tsconfig.build.tsbuildinfo +0 -1
  53. package/tsconfig.json +0 -7
  54. package/tsconfig.tests.json +0 -7
@@ -1,1243 +0,0 @@
1
- import assert from 'node:assert'
2
- import { CID } from 'multiformats/cid'
3
- import { LexiconDoc, Lexicons, parseLexiconDoc } from '../src/index.js'
4
- import LexiconDocs from './_scaffolds/lexicons.js'
5
-
6
- describe('Lexicons collection', () => {
7
- const lex = new Lexicons(LexiconDocs)
8
-
9
- it('Adds schemas', () => {
10
- expect(() => lex.add(LexiconDocs[0])).toThrow()
11
- })
12
-
13
- it('Correctly references all definitions', () => {
14
- expect(lex.getDef('com.example.kitchenSink')).toEqual(
15
- LexiconDocs[0].defs.main,
16
- )
17
- expect(lex.getDef('lex:com.example.kitchenSink')).toEqual(
18
- LexiconDocs[0].defs.main,
19
- )
20
- expect(lex.getDef('com.example.kitchenSink#main')).toEqual(
21
- LexiconDocs[0].defs.main,
22
- )
23
- expect(lex.getDef('lex:com.example.kitchenSink#main')).toEqual(
24
- LexiconDocs[0].defs.main,
25
- )
26
- expect(lex.getDef('com.example.kitchenSink#object')).toEqual(
27
- LexiconDocs[0].defs.object,
28
- )
29
- expect(lex.getDef('lex:com.example.kitchenSink#object')).toEqual(
30
- LexiconDocs[0].defs.object,
31
- )
32
- })
33
- })
34
-
35
- describe('General validation', () => {
36
- const lex = new Lexicons(LexiconDocs)
37
- it('Validates records correctly', () => {
38
- {
39
- const res = lex.validate('com.example.kitchenSink', {
40
- $type: 'com.example.kitchenSink',
41
- object: {
42
- object: { boolean: true },
43
- array: ['one', 'two'],
44
- boolean: true,
45
- integer: 123,
46
- string: 'string',
47
- },
48
- array: ['one', 'two'],
49
- boolean: true,
50
- integer: 123,
51
- string: 'string',
52
- datetime: new Date().toISOString(),
53
- atUri: 'at://did:web:example.com/com.example.test/self',
54
- did: 'did:web:example.com',
55
- cid: 'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a',
56
- bytes: new Uint8Array([0, 1, 2, 3]),
57
- cidLink: CID.parse(
58
- 'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a',
59
- ),
60
- })
61
- expect(res.success).toBe(true)
62
- }
63
- {
64
- const res = lex.validate('com.example.kitchenSink', {})
65
- expect(res.success).toBe(false)
66
- if (res.success) throw new Error('Asserted')
67
- expect(res.error?.message).toBe('Record must have the property "object"')
68
- }
69
- })
70
- it('Validates objects correctly', () => {
71
- {
72
- const res = lex.validate('com.example.kitchenSink#object', {
73
- object: { boolean: true },
74
- array: ['one', 'two'],
75
- boolean: true,
76
- integer: 123,
77
- string: 'string',
78
- })
79
- expect(res.success).toBe(true)
80
- }
81
- {
82
- const res = lex.validate('com.example.kitchenSink#object', {})
83
- assert(!res.success)
84
- expect(res.error.message).toBe('Object must have the property "object"')
85
- }
86
- })
87
- it('fails when a required property is missing', () => {
88
- const schema = {
89
- lexicon: 1,
90
- id: 'com.example.kitchenSink',
91
- defs: {
92
- test: {
93
- type: 'object',
94
- required: ['foo'],
95
- properties: {},
96
- },
97
- },
98
- }
99
- expect(() => {
100
- parseLexiconDoc(schema)
101
- }).toThrow('Required field \\"foo\\" not defined')
102
- })
103
- it('allows unknown fields to be present', () => {
104
- const schema = {
105
- lexicon: 1,
106
- id: 'com.example.unknownFields',
107
- defs: {
108
- test: {
109
- type: 'object',
110
- properties: {},
111
- foo: 3,
112
- },
113
- },
114
- }
115
-
116
- expect(() => {
117
- parseLexiconDoc(schema)
118
- }).not.toThrow()
119
- })
120
- it('fails lexicon parsing when uri is invalid', () => {
121
- const schema: LexiconDoc = {
122
- lexicon: 1,
123
- id: 'com.example.invalidUri',
124
- defs: {
125
- main: {
126
- type: 'object',
127
- properties: {
128
- test: { type: 'ref', ref: 'com.example.invalid#test#test' },
129
- },
130
- },
131
- },
132
- }
133
-
134
- expect(() => {
135
- new Lexicons([schema])
136
- }).toThrow('Uri can only have one hash segment')
137
- })
138
- it('fails validation when ref uri has multiple hash segments', () => {
139
- const schema: LexiconDoc = {
140
- lexicon: 1,
141
- id: 'com.example.invalidUri',
142
- defs: {
143
- main: {
144
- type: 'object',
145
- properties: {
146
- test: { type: 'integer' },
147
- },
148
- },
149
- object: {
150
- type: 'object',
151
- required: ['test'],
152
- properties: {
153
- test: {
154
- type: 'union',
155
- refs: ['com.example.invalidUri'],
156
- },
157
- },
158
- },
159
- },
160
- }
161
- const lexicons = new Lexicons([schema])
162
- expect(() => {
163
- lexicons.validate('com.example.invalidUri#object', {
164
- test: {
165
- $type: 'com.example.invalidUri#main#main',
166
- test: 123,
167
- },
168
- })
169
- }).toThrow('Uri can only have one hash segment')
170
- })
171
- it('union handles both implicit and explicit #main', () => {
172
- const schemas: LexiconDoc[] = [
173
- {
174
- lexicon: 1,
175
- id: 'com.example.implicitMain',
176
- defs: {
177
- main: {
178
- type: 'object',
179
- required: ['test'],
180
- properties: {
181
- test: { type: 'string' },
182
- },
183
- },
184
- },
185
- },
186
- {
187
- lexicon: 1,
188
- id: 'com.example.testImplicitMain',
189
- defs: {
190
- main: {
191
- type: 'object',
192
- required: ['union'],
193
- properties: {
194
- union: {
195
- type: 'union',
196
- refs: ['com.example.implicitMain'],
197
- },
198
- },
199
- },
200
- },
201
- },
202
- {
203
- lexicon: 1,
204
- id: 'com.example.testExplicitMain',
205
- defs: {
206
- main: {
207
- type: 'object',
208
- required: ['union'],
209
- properties: {
210
- union: {
211
- type: 'union',
212
- refs: ['com.example.implicitMain#main'],
213
- },
214
- },
215
- },
216
- },
217
- },
218
- ]
219
-
220
- const lexicon = new Lexicons(schemas)
221
-
222
- let result = lexicon.validate('com.example.testImplicitMain', {
223
- union: {
224
- $type: 'com.example.implicitMain',
225
- test: 123,
226
- },
227
- })
228
- expect(result.success).toBeFalsy()
229
- expect(result['error']?.message).toBe('Object/union/test must be a string')
230
-
231
- result = lexicon.validate('com.example.testImplicitMain', {
232
- union: {
233
- $type: 'com.example.implicitMain#main',
234
- test: 123,
235
- },
236
- })
237
- expect(result.success).toBeFalsy()
238
- expect(result['error']?.message).toBe('Object/union/test must be a string')
239
-
240
- result = lexicon.validate('com.example.testExplicitMain', {
241
- union: {
242
- $type: 'com.example.implicitMain',
243
- test: 123,
244
- },
245
- })
246
- expect(result.success).toBeFalsy()
247
- expect(result['error']?.message).toBe('Object/union/test must be a string')
248
-
249
- result = lexicon.validate('com.example.testExplicitMain', {
250
- union: {
251
- $type: 'com.example.implicitMain#main',
252
- test: 123,
253
- },
254
- })
255
- expect(result.success).toBeFalsy()
256
- expect(result['error']?.message).toBe('Object/union/test must be a string')
257
- })
258
- })
259
-
260
- describe('Record validation', () => {
261
- const lex = new Lexicons(LexiconDocs)
262
-
263
- const passingSink = {
264
- $type: 'com.example.kitchenSink',
265
- object: {
266
- object: { boolean: true },
267
- array: ['one', 'two'],
268
- boolean: true,
269
- integer: 123,
270
- string: 'string',
271
- },
272
- array: ['one', 'two'],
273
- boolean: true,
274
- integer: 123,
275
- string: 'string',
276
- bytes: new Uint8Array([0, 1, 2, 3]),
277
- cidLink: CID.parse(
278
- 'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a',
279
- ),
280
- }
281
-
282
- it('Passes valid schemas', () => {
283
- lex.assertValidRecord('com.example.kitchenSink', passingSink)
284
- })
285
-
286
- it('Fails invalid input types', () => {
287
- expect(() =>
288
- lex.assertValidRecord('com.example.kitchenSink', undefined),
289
- ).toThrow('Record must be an object')
290
- expect(() =>
291
- lex.assertValidRecord('com.example.kitchenSink', 1234),
292
- ).toThrow('Record must be an object')
293
- expect(() =>
294
- lex.assertValidRecord('com.example.kitchenSink', 'string'),
295
- ).toThrow('Record must be an object')
296
- })
297
-
298
- it('Fails incorrect $type', () => {
299
- expect(() => lex.assertValidRecord('com.example.kitchenSink', {})).toThrow(
300
- 'Record/$type must be a string',
301
- )
302
- expect(() =>
303
- lex.assertValidRecord('com.example.kitchenSink', { $type: 'foo' }),
304
- ).toThrow('Invalid $type: must be lex:com.example.kitchenSink, got foo')
305
- })
306
-
307
- it('Fails missing required', () => {
308
- expect(() =>
309
- lex.assertValidRecord('com.example.kitchenSink', {
310
- $type: 'com.example.kitchenSink',
311
- array: ['one', 'two'],
312
- boolean: true,
313
- integer: 123,
314
- string: 'string',
315
- datetime: new Date().toISOString(),
316
- atUri: 'at://did:web:example.com/com.example.test/self',
317
- did: 'did:web:example.com',
318
- cid: 'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a',
319
- bytes: new Uint8Array([0, 1, 2, 3]),
320
- cidLink: CID.parse(
321
- 'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a',
322
- ),
323
- }),
324
- ).toThrow('Record must have the property "object"')
325
- expect(() =>
326
- lex.assertValidRecord('com.example.kitchenSink', {
327
- ...passingSink,
328
- object: undefined,
329
- }),
330
- ).toThrow('Record must have the property "object"')
331
- })
332
-
333
- it('Fails incorrect types', () => {
334
- expect(() =>
335
- lex.assertValidRecord('com.example.kitchenSink', {
336
- ...passingSink,
337
- object: {
338
- ...passingSink.object,
339
- object: { boolean: '1234' },
340
- },
341
- }),
342
- ).toThrow('Record/object/object/boolean must be a boolean')
343
- expect(() =>
344
- lex.assertValidRecord('com.example.kitchenSink', {
345
- ...passingSink,
346
- object: true,
347
- }),
348
- ).toThrow('Record/object must be an object')
349
- expect(() =>
350
- lex.assertValidRecord('com.example.kitchenSink', {
351
- ...passingSink,
352
- array: 1234,
353
- }),
354
- ).toThrow('Record/array must be an array')
355
- expect(() =>
356
- lex.assertValidRecord('com.example.kitchenSink', {
357
- ...passingSink,
358
- integer: true,
359
- }),
360
- ).toThrow('Record/integer must be an integer')
361
- expect(() =>
362
- lex.assertValidRecord('com.example.kitchenSink', {
363
- ...passingSink,
364
- string: {},
365
- }),
366
- ).toThrow('Record/string must be a string')
367
- expect(() =>
368
- lex.assertValidRecord('com.example.kitchenSink', {
369
- ...passingSink,
370
- bytes: 1234,
371
- }),
372
- ).toThrow('Record/bytes must be a byte array')
373
- expect(() =>
374
- lex.assertValidRecord('com.example.kitchenSink', {
375
- ...passingSink,
376
- cidLink: 'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a',
377
- }),
378
- ).toThrow('Record/cidLink must be a CID')
379
- })
380
-
381
- it('Handles optional properties correctly', () => {
382
- lex.assertValidRecord('com.example.optional', {
383
- $type: 'com.example.optional',
384
- })
385
- })
386
-
387
- it('Handles default properties correctly', () => {
388
- const result = lex.assertValidRecord('com.example.default', {
389
- $type: 'com.example.default',
390
- object: {},
391
- })
392
- expect(result).toEqual({
393
- $type: 'com.example.default',
394
- boolean: false,
395
- integer: 0,
396
- string: '',
397
- object: {
398
- boolean: true,
399
- integer: 1,
400
- string: 'x',
401
- },
402
- })
403
- expect(result).not.toHaveProperty('datetime')
404
- })
405
-
406
- it('Handles unions correctly', () => {
407
- lex.assertValidRecord('com.example.union', {
408
- $type: 'com.example.union',
409
- unionOpen: {
410
- $type: 'com.example.kitchenSink#object',
411
- object: { boolean: true },
412
- array: ['one', 'two'],
413
- boolean: true,
414
- integer: 123,
415
- string: 'string',
416
- },
417
- unionClosed: {
418
- $type: 'com.example.kitchenSink#subobject',
419
- boolean: true,
420
- },
421
- })
422
- lex.assertValidRecord('com.example.union', {
423
- $type: 'com.example.union',
424
- unionOpen: {
425
- $type: 'com.example.other',
426
- },
427
- unionClosed: {
428
- $type: 'com.example.kitchenSink#subobject',
429
- boolean: true,
430
- },
431
- })
432
- expect(() =>
433
- lex.assertValidRecord('com.example.union', {
434
- $type: 'com.example.union',
435
- unionOpen: {},
436
- unionClosed: {},
437
- }),
438
- ).toThrow(
439
- 'Record/unionOpen must be an object which includes the "$type" property',
440
- )
441
- expect(() =>
442
- lex.assertValidRecord('com.example.union', {
443
- $type: 'com.example.union',
444
- unionOpen: {
445
- $type: 'com.example.other',
446
- },
447
- unionClosed: {
448
- $type: 'com.example.other',
449
- boolean: true,
450
- },
451
- }),
452
- ).toThrow(
453
- 'Record/unionClosed $type must be one of lex:com.example.kitchenSink#object, lex:com.example.kitchenSink#subobject',
454
- )
455
- })
456
-
457
- it('Handles unknowns correctly', () => {
458
- lex.assertValidRecord('com.example.unknown', {
459
- $type: 'com.example.unknown',
460
- unknown: { foo: 'bar' },
461
- })
462
- expect(() =>
463
- lex.assertValidRecord('com.example.unknown', {
464
- $type: 'com.example.unknown',
465
- }),
466
- ).toThrow('Record must have the property "unknown"')
467
- })
468
-
469
- it('Applies array length constraints', () => {
470
- lex.assertValidRecord('com.example.arrayLength', {
471
- $type: 'com.example.arrayLength',
472
- array: [1, 2, 3],
473
- })
474
- expect(() =>
475
- lex.assertValidRecord('com.example.arrayLength', {
476
- $type: 'com.example.arrayLength',
477
- array: [1],
478
- }),
479
- ).toThrow('Record/array must not have fewer than 2 elements')
480
- expect(() =>
481
- lex.assertValidRecord('com.example.arrayLength', {
482
- $type: 'com.example.arrayLength',
483
- array: [1, 2, 3, 4, 5],
484
- }),
485
- ).toThrow('Record/array must not have more than 4 elements')
486
- })
487
-
488
- it('Applies array item constraints', () => {
489
- expect(() =>
490
- lex.assertValidRecord('com.example.arrayLength', {
491
- $type: 'com.example.arrayLength',
492
- array: [1, '2', 3],
493
- }),
494
- ).toThrow('Record/array/1 must be an integer')
495
- expect(() =>
496
- lex.assertValidRecord('com.example.arrayLength', {
497
- $type: 'com.example.arrayLength',
498
- array: [1, undefined, 3],
499
- }),
500
- ).toThrow('Record/array/1 must be an integer')
501
- })
502
-
503
- it('Applies boolean const constraint', () => {
504
- lex.assertValidRecord('com.example.boolConst', {
505
- $type: 'com.example.boolConst',
506
- boolean: false,
507
- })
508
- expect(() =>
509
- lex.assertValidRecord('com.example.boolConst', {
510
- $type: 'com.example.boolConst',
511
- boolean: true,
512
- }),
513
- ).toThrow('Record/boolean must be false')
514
- })
515
-
516
- it('Applies integer range constraint', () => {
517
- lex.assertValidRecord('com.example.integerRange', {
518
- $type: 'com.example.integerRange',
519
- integer: 2,
520
- })
521
- expect(() =>
522
- lex.assertValidRecord('com.example.integerRange', {
523
- $type: 'com.example.integerRange',
524
- integer: 1,
525
- }),
526
- ).toThrow('Record/integer can not be less than 2')
527
- expect(() =>
528
- lex.assertValidRecord('com.example.integerRange', {
529
- $type: 'com.example.integerRange',
530
- integer: 5,
531
- }),
532
- ).toThrow('Record/integer can not be greater than 4')
533
- })
534
-
535
- it('Applies integer enum constraint', () => {
536
- lex.assertValidRecord('com.example.integerEnum', {
537
- $type: 'com.example.integerEnum',
538
- integer: 2,
539
- })
540
- expect(() =>
541
- lex.assertValidRecord('com.example.integerEnum', {
542
- $type: 'com.example.integerEnum',
543
- integer: 0,
544
- }),
545
- ).toThrow('Record/integer must be one of (1|2)')
546
- })
547
-
548
- it('Applies integer const constraint', () => {
549
- lex.assertValidRecord('com.example.integerConst', {
550
- $type: 'com.example.integerConst',
551
- integer: 0,
552
- })
553
- expect(() =>
554
- lex.assertValidRecord('com.example.integerConst', {
555
- $type: 'com.example.integerConst',
556
- integer: 1,
557
- }),
558
- ).toThrow('Record/integer must be 0')
559
- })
560
-
561
- it('Applies integer whole-number constraint', () => {
562
- expect(() =>
563
- lex.assertValidRecord('com.example.integerRange', {
564
- $type: 'com.example.integerRange',
565
- integer: 2.5,
566
- }),
567
- ).toThrow('Record/integer must be an integer')
568
- })
569
-
570
- it('Applies string length constraint', () => {
571
- // Shorter than two UTF8 characters
572
- expect(() =>
573
- lex.assertValidRecord('com.example.stringLength', {
574
- $type: 'com.example.stringLength',
575
- string: '',
576
- }),
577
- ).toThrow('Record/string must not be shorter than 2 characters')
578
- expect(() =>
579
- lex.assertValidRecord('com.example.stringLength', {
580
- $type: 'com.example.stringLength',
581
- string: 'a',
582
- }),
583
- ).toThrow('Record/string must not be shorter than 2 characters')
584
-
585
- // Two to four UTF8 characters
586
- lex.assertValidRecord('com.example.stringLength', {
587
- $type: 'com.example.stringLength',
588
- string: 'ab',
589
- })
590
- lex.assertValidRecord('com.example.stringLength', {
591
- $type: 'com.example.stringLength',
592
- string: '\u0301', // Combining acute accent (2 bytes)
593
- })
594
- lex.assertValidRecord('com.example.stringLength', {
595
- $type: 'com.example.stringLength',
596
- string: 'a\u0301', // 'a' + combining acute accent (1 + 2 bytes = 3 bytes)
597
- })
598
- lex.assertValidRecord('com.example.stringLength', {
599
- $type: 'com.example.stringLength',
600
- string: 'aé', // 'a' (1 byte) + 'é' (2 bytes) = 3 bytes
601
- })
602
- lex.assertValidRecord('com.example.stringLength', {
603
- $type: 'com.example.stringLength',
604
- string: 'abc',
605
- })
606
- lex.assertValidRecord('com.example.stringLength', {
607
- $type: 'com.example.stringLength',
608
- string: '一', // CJK character (3 bytes)
609
- })
610
- lex.assertValidRecord('com.example.stringLength', {
611
- $type: 'com.example.stringLength',
612
- string: '\uD83D', // Unpaired high surrogate (3 bytes)
613
- })
614
- lex.assertValidRecord('com.example.stringLength', {
615
- $type: 'com.example.stringLength',
616
- string: 'abcd',
617
- })
618
- lex.assertValidRecord('com.example.stringLength', {
619
- $type: 'com.example.stringLength',
620
- string: 'éé', // 'é' + 'é' (2 + 2 bytes = 4 bytes)
621
- })
622
- lex.assertValidRecord('com.example.stringLength', {
623
- $type: 'com.example.stringLength',
624
- string: 'aaé', // 1 + 1 + 2 = 4 bytes
625
- })
626
- lex.assertValidRecord('com.example.stringLength', {
627
- $type: 'com.example.stringLength',
628
- string: '👋', // 4 bytes
629
- })
630
-
631
- expect(() =>
632
- lex.assertValidRecord('com.example.stringLength', {
633
- $type: 'com.example.stringLength',
634
- string: 'abcde',
635
- }),
636
- ).toThrow('Record/string must not be longer than 4 characters')
637
- expect(() =>
638
- lex.assertValidRecord('com.example.stringLength', {
639
- $type: 'com.example.stringLength',
640
- string: 'a\u0301\u0301', // 1 + (2 * 2) = 5 bytes
641
- }),
642
- ).toThrow('Record/string must not be longer than 4 characters')
643
- expect(() =>
644
- lex.assertValidRecord('com.example.stringLength', {
645
- $type: 'com.example.stringLength',
646
- string: '\uD83D\uD83D', // Two unpaired high surrogates (3 * 2 = 6 bytes)
647
- }),
648
- ).toThrow('Record/string must not be longer than 4 characters')
649
- expect(() =>
650
- lex.assertValidRecord('com.example.stringLength', {
651
- $type: 'com.example.stringLength',
652
- string: 'ééé', // 2 + 2 + 2 bytes = 6 bytes
653
- }),
654
- ).toThrow('Record/string must not be longer than 4 characters')
655
- expect(() =>
656
- lex.assertValidRecord('com.example.stringLength', {
657
- $type: 'com.example.stringLength',
658
- string: '👋a', // 4 + 1 bytes = 5 bytes
659
- }),
660
- ).toThrow('Record/string must not be longer than 4 characters')
661
- expect(() =>
662
- lex.assertValidRecord('com.example.stringLength', {
663
- $type: 'com.example.stringLength',
664
- string: '👨👨', // 4 + 4 = 8 bytes
665
- }),
666
- ).toThrow('Record/string must not be longer than 4 characters')
667
- expect(() =>
668
- lex.assertValidRecord('com.example.stringLength', {
669
- $type: 'com.example.stringLength',
670
- string: '👨‍👩‍👧‍👧', // 4 emojis × 4 bytes + 3 ZWJs × 3 bytes = 25 bytes
671
- }),
672
- ).toThrow('Record/string must not be longer than 4 characters')
673
- })
674
-
675
- it('Applies string length constraint (no minLength)', () => {
676
- // Shorter than two UTF8 characters
677
- lex.assertValidRecord('com.example.stringLengthNoMinLength', {
678
- $type: 'com.example.stringLengthNoMinLength',
679
- string: '',
680
- })
681
- lex.assertValidRecord('com.example.stringLengthNoMinLength', {
682
- $type: 'com.example.stringLengthNoMinLength',
683
- string: 'a',
684
- })
685
-
686
- // Two to four UTF8 characters
687
- lex.assertValidRecord('com.example.stringLengthNoMinLength', {
688
- $type: 'com.example.stringLengthNoMinLength',
689
- string: 'ab',
690
- })
691
- lex.assertValidRecord('com.example.stringLengthNoMinLength', {
692
- $type: 'com.example.stringLengthNoMinLength',
693
- string: '\u0301', // Combining acute accent (2 bytes)
694
- })
695
- lex.assertValidRecord('com.example.stringLengthNoMinLength', {
696
- $type: 'com.example.stringLengthNoMinLength',
697
- string: 'a\u0301', // 'a' + combining acute accent (1 + 2 bytes = 3 bytes)
698
- })
699
- lex.assertValidRecord('com.example.stringLengthNoMinLength', {
700
- $type: 'com.example.stringLengthNoMinLength',
701
- string: 'aé', // 'a' (1 byte) + 'é' (2 bytes) = 3 bytes
702
- })
703
- lex.assertValidRecord('com.example.stringLengthNoMinLength', {
704
- $type: 'com.example.stringLengthNoMinLength',
705
- string: 'abc',
706
- })
707
- lex.assertValidRecord('com.example.stringLengthNoMinLength', {
708
- $type: 'com.example.stringLengthNoMinLength',
709
- string: '一', // CJK character (3 bytes)
710
- })
711
- lex.assertValidRecord('com.example.stringLengthNoMinLength', {
712
- $type: 'com.example.stringLengthNoMinLength',
713
- string: '\uD83D', // Unpaired high surrogate (3 bytes)
714
- })
715
- lex.assertValidRecord('com.example.stringLengthNoMinLength', {
716
- $type: 'com.example.stringLengthNoMinLength',
717
- string: 'abcd',
718
- })
719
- lex.assertValidRecord('com.example.stringLengthNoMinLength', {
720
- $type: 'com.example.stringLengthNoMinLength',
721
- string: 'éé', // 'é' + 'é' (2 + 2 bytes = 4 bytes)
722
- })
723
- lex.assertValidRecord('com.example.stringLengthNoMinLength', {
724
- $type: 'com.example.stringLengthNoMinLength',
725
- string: 'aaé', // 1 + 1 + 2 = 4 bytes
726
- })
727
- lex.assertValidRecord('com.example.stringLengthNoMinLength', {
728
- $type: 'com.example.stringLengthNoMinLength',
729
- string: '👋', // 4 bytes
730
- })
731
-
732
- expect(() =>
733
- lex.assertValidRecord('com.example.stringLengthNoMinLength', {
734
- $type: 'com.example.stringLengthNoMinLength',
735
- string: 'abcde',
736
- }),
737
- ).toThrow('Record/string must not be longer than 4 characters')
738
- expect(() =>
739
- lex.assertValidRecord('com.example.stringLengthNoMinLength', {
740
- $type: 'com.example.stringLengthNoMinLength',
741
- string: 'a\u0301\u0301', // 1 + (2 * 2) = 5 bytes
742
- }),
743
- ).toThrow('Record/string must not be longer than 4 characters')
744
- expect(() =>
745
- lex.assertValidRecord('com.example.stringLengthNoMinLength', {
746
- $type: 'com.example.stringLengthNoMinLength',
747
- string: '\uD83D\uD83D', // Two unpaired high surrogates (3 * 2 = 6 bytes)
748
- }),
749
- ).toThrow('Record/string must not be longer than 4 characters')
750
- expect(() =>
751
- lex.assertValidRecord('com.example.stringLengthNoMinLength', {
752
- $type: 'com.example.stringLengthNoMinLength',
753
- string: 'ééé', // 2 + 2 + 2 bytes = 6 bytes
754
- }),
755
- ).toThrow('Record/string must not be longer than 4 characters')
756
- expect(() =>
757
- lex.assertValidRecord('com.example.stringLengthNoMinLength', {
758
- $type: 'com.example.stringLengthNoMinLength',
759
- string: '👋a', // 4 + 1 bytes = 5 bytes
760
- }),
761
- ).toThrow('Record/string must not be longer than 4 characters')
762
- expect(() =>
763
- lex.assertValidRecord('com.example.stringLengthNoMinLength', {
764
- $type: 'com.example.stringLengthNoMinLength',
765
- string: '👨👨', // 4 + 4 = 8 bytes
766
- }),
767
- ).toThrow('Record/string must not be longer than 4 characters')
768
- expect(() =>
769
- lex.assertValidRecord('com.example.stringLengthNoMinLength', {
770
- $type: 'com.example.stringLengthNoMinLength',
771
- string: '👨‍👩‍👧‍👧', // 4 emojis × 4 bytes + 3 ZWJs × 3 bytes = 25 bytes
772
- }),
773
- ).toThrow('Record/string must not be longer than 4 characters')
774
- })
775
-
776
- it('Applies grapheme string length constraint', () => {
777
- // Shorter than two graphemes
778
- expect(() =>
779
- lex.assertValidRecord('com.example.stringLengthGrapheme', {
780
- $type: 'com.example.stringLengthGrapheme',
781
- string: '',
782
- }),
783
- ).toThrow('Record/string must not be shorter than 2 graphemes')
784
- expect(() =>
785
- lex.assertValidRecord('com.example.stringLengthGrapheme', {
786
- $type: 'com.example.stringLengthGrapheme',
787
- string: '\u0301\u0301\u0301', // Three combining acute accents
788
- }),
789
- ).toThrow('Record/string must not be shorter than 2 graphemes')
790
- expect(() =>
791
- lex.assertValidRecord('com.example.stringLengthGrapheme', {
792
- $type: 'com.example.stringLengthGrapheme',
793
- string: 'a',
794
- }),
795
- ).toThrow('Record/string must not be shorter than 2 graphemes')
796
- expect(() =>
797
- lex.assertValidRecord('com.example.stringLengthGrapheme', {
798
- $type: 'com.example.stringLengthGrapheme',
799
- string: 'a\u0301\u0301\u0301\u0301', // 'á́́́' ('a' with four combining acute accents)
800
- }),
801
- ).toThrow('Record/string must not be shorter than 2 graphemes')
802
- expect(() =>
803
- lex.assertValidRecord('com.example.stringLengthGrapheme', {
804
- $type: 'com.example.stringLengthGrapheme',
805
- string: '5\uFE0F', // '5️' with emoji presentation
806
- }),
807
- ).toThrow('Record/string must not be shorter than 2 graphemes')
808
- expect(() =>
809
- lex.assertValidRecord('com.example.stringLengthGrapheme', {
810
- $type: 'com.example.stringLengthGrapheme',
811
- string: '👨‍👩‍👧‍👧',
812
- }),
813
- ).toThrow('Record/string must not be shorter than 2 graphemes')
814
-
815
- // Two to four graphemes
816
- lex.assertValidRecord('com.example.stringLengthGrapheme', {
817
- $type: 'com.example.stringLengthGrapheme',
818
- string: 'ab',
819
- })
820
- lex.assertValidRecord('com.example.stringLengthGrapheme', {
821
- $type: 'com.example.stringLengthGrapheme',
822
- string: 'a\u0301b', // 'áb' with combining accent
823
- })
824
- lex.assertValidRecord('com.example.stringLengthGrapheme', {
825
- $type: 'com.example.stringLengthGrapheme',
826
- string: 'a\u0301b\u0301', // 'áb́'
827
- })
828
- lex.assertValidRecord('com.example.stringLengthGrapheme', {
829
- $type: 'com.example.stringLengthGrapheme',
830
- string: '😀😀',
831
- })
832
- lex.assertValidRecord('com.example.stringLengthGrapheme', {
833
- $type: 'com.example.stringLengthGrapheme',
834
- string: '12👨‍👩‍👧‍👧',
835
- })
836
- lex.assertValidRecord('com.example.stringLengthGrapheme', {
837
- $type: 'com.example.stringLengthGrapheme',
838
- string: 'abcd',
839
- })
840
- lex.assertValidRecord('com.example.stringLengthGrapheme', {
841
- $type: 'com.example.stringLengthGrapheme',
842
- string: 'a\u0301b\u0301c\u0301d\u0301', // 'áb́ćd́'
843
- })
844
-
845
- // Longer than four graphemes
846
- expect(() =>
847
- lex.assertValidRecord('com.example.stringLengthGrapheme', {
848
- $type: 'com.example.stringLengthGrapheme',
849
- string: 'abcde',
850
- }),
851
- ).toThrow('Record/string must not be longer than 4 graphemes')
852
- expect(() =>
853
- lex.assertValidRecord('com.example.stringLengthGrapheme', {
854
- $type: 'com.example.stringLengthGrapheme',
855
- string: 'a\u0301b\u0301c\u0301d\u0301e\u0301', // 'áb́ćd́é'
856
- }),
857
- ).toThrow('Record/string must not be longer than 4 graphemes')
858
- expect(() =>
859
- lex.assertValidRecord('com.example.stringLengthGrapheme', {
860
- $type: 'com.example.stringLengthGrapheme',
861
- string: '😀😀😀😀😀',
862
- }),
863
- ).toThrow('Record/string must not be longer than 4 graphemes')
864
- expect(() =>
865
- lex.assertValidRecord('com.example.stringLengthGrapheme', {
866
- $type: 'com.example.stringLengthGrapheme',
867
- string: 'ab😀de',
868
- }),
869
- ).toThrow('Record/string must not be longer than 4 graphemes')
870
- })
871
-
872
- it('Applies string enum constraint', () => {
873
- lex.assertValidRecord('com.example.stringEnum', {
874
- $type: 'com.example.stringEnum',
875
- string: 'a',
876
- })
877
- expect(() =>
878
- lex.assertValidRecord('com.example.stringEnum', {
879
- $type: 'com.example.stringEnum',
880
- string: 'c',
881
- }),
882
- ).toThrow('Record/string must be one of (a|b)')
883
- })
884
-
885
- it('Applies string const constraint', () => {
886
- lex.assertValidRecord('com.example.stringConst', {
887
- $type: 'com.example.stringConst',
888
- string: 'a',
889
- })
890
- expect(() =>
891
- lex.assertValidRecord('com.example.stringConst', {
892
- $type: 'com.example.stringConst',
893
- string: 'b',
894
- }),
895
- ).toThrow('Record/string must be a')
896
- })
897
-
898
- it('Applies datetime formatting constraint', () => {
899
- for (const datetime of [
900
- '2022-12-12T00:50:36.809Z',
901
- '2022-12-12T00:50:36Z',
902
- '2022-12-12T00:50:36.8Z',
903
- '2022-12-12T00:50:36.80Z',
904
- '2022-12-12T00:50:36+00:00',
905
- '2022-12-12T00:50:36.8+00:00',
906
- '2022-12-11T19:50:36-05:00',
907
- '2022-12-11T19:50:36.8-05:00',
908
- '2022-12-11T19:50:36.80-05:00',
909
- '2022-12-11T19:50:36.809-05:00',
910
- ]) {
911
- lex.assertValidRecord('com.example.datetime', {
912
- $type: 'com.example.datetime',
913
- datetime,
914
- })
915
- }
916
- expect(() =>
917
- lex.assertValidRecord('com.example.datetime', {
918
- $type: 'com.example.datetime',
919
- datetime: 'bad date',
920
- }),
921
- ).toThrow(
922
- 'Record/datetime must be an valid atproto datetime (both RFC-3339 and ISO-8601)',
923
- )
924
- })
925
-
926
- it('Applies uri formatting constraint', () => {
927
- for (const uri of [
928
- 'https://example.com',
929
- 'https://example.com/with/path',
930
- 'https://example.com/with/path?and=query',
931
- 'at://bsky.social',
932
- 'did:example:test',
933
- ]) {
934
- lex.assertValidRecord('com.example.uri', {
935
- $type: 'com.example.uri',
936
- uri,
937
- })
938
- }
939
- expect(() =>
940
- lex.assertValidRecord('com.example.uri', {
941
- $type: 'com.example.uri',
942
- uri: 'not a uri',
943
- }),
944
- ).toThrow('Record/uri must be a uri')
945
- })
946
-
947
- it('Applies at-uri formatting constraint', () => {
948
- lex.assertValidRecord('com.example.atUri', {
949
- $type: 'com.example.atUri',
950
- atUri: 'at://did:web:example.com/com.example.test/self',
951
- })
952
- expect(() =>
953
- lex.assertValidRecord('com.example.atUri', {
954
- $type: 'com.example.atUri',
955
- atUri: 'http://not-atproto.com',
956
- }),
957
- ).toThrow('Record/atUri must be a valid at-uri')
958
- })
959
-
960
- it('Applies did formatting constraint', () => {
961
- lex.assertValidRecord('com.example.did', {
962
- $type: 'com.example.did',
963
- did: 'did:web:example.com',
964
- })
965
- lex.assertValidRecord('com.example.did', {
966
- $type: 'com.example.did',
967
- did: 'did:plc:12345678abcdefghijklmnop',
968
- })
969
-
970
- expect(() =>
971
- lex.assertValidRecord('com.example.did', {
972
- $type: 'com.example.did',
973
- did: 'bad did',
974
- }),
975
- ).toThrow('Record/did must be a valid did')
976
- expect(() =>
977
- lex.assertValidRecord('com.example.did', {
978
- $type: 'com.example.did',
979
- did: 'did:short',
980
- }),
981
- ).toThrow('Record/did must be a valid did')
982
- })
983
-
984
- it('Applies handle formatting constraint', () => {
985
- lex.assertValidRecord('com.example.handle', {
986
- $type: 'com.example.handle',
987
- handle: 'test.bsky.social',
988
- })
989
- lex.assertValidRecord('com.example.handle', {
990
- $type: 'com.example.handle',
991
- handle: 'bsky.test',
992
- })
993
-
994
- expect(() =>
995
- lex.assertValidRecord('com.example.handle', {
996
- $type: 'com.example.handle',
997
- handle: 'bad handle',
998
- }),
999
- ).toThrow('Record/handle must be a valid handle')
1000
- expect(() =>
1001
- lex.assertValidRecord('com.example.handle', {
1002
- $type: 'com.example.handle',
1003
- handle: '-bad-.test',
1004
- }),
1005
- ).toThrow('Record/handle must be a valid handle')
1006
- })
1007
-
1008
- it('Applies at-identifier formatting constraint', () => {
1009
- lex.assertValidRecord('com.example.atIdentifier', {
1010
- $type: 'com.example.atIdentifier',
1011
- atIdentifier: 'bsky.test',
1012
- })
1013
- lex.assertValidRecord('com.example.atIdentifier', {
1014
- $type: 'com.example.atIdentifier',
1015
- atIdentifier: 'did:plc:12345678abcdefghijklmnop',
1016
- })
1017
-
1018
- expect(() =>
1019
- lex.assertValidRecord('com.example.atIdentifier', {
1020
- $type: 'com.example.atIdentifier',
1021
- atIdentifier: 'bad id',
1022
- }),
1023
- ).toThrow('Record/atIdentifier must be a valid did or a handle')
1024
- expect(() =>
1025
- lex.assertValidRecord('com.example.atIdentifier', {
1026
- $type: 'com.example.atIdentifier',
1027
- atIdentifier: '-bad-.test',
1028
- }),
1029
- ).toThrow('Record/atIdentifier must be a valid did or a handle')
1030
- })
1031
-
1032
- it('Applies nsid formatting constraint', () => {
1033
- lex.assertValidRecord('com.example.nsid', {
1034
- $type: 'com.example.nsid',
1035
- nsid: 'com.atproto.test',
1036
- })
1037
- lex.assertValidRecord('com.example.nsid', {
1038
- $type: 'com.example.nsid',
1039
- nsid: 'app.bsky.nested.test',
1040
- })
1041
-
1042
- expect(() =>
1043
- lex.assertValidRecord('com.example.nsid', {
1044
- $type: 'com.example.nsid',
1045
- nsid: 'bad nsid',
1046
- }),
1047
- ).toThrow('Record/nsid must be a valid nsid')
1048
- expect(() =>
1049
- lex.assertValidRecord('com.example.nsid', {
1050
- $type: 'com.example.nsid',
1051
- nsid: 'com.bad-.foo',
1052
- }),
1053
- ).toThrow('Record/nsid must be a valid nsid')
1054
- })
1055
-
1056
- it('Applies cid formatting constraint', () => {
1057
- lex.assertValidRecord('com.example.cid', {
1058
- $type: 'com.example.cid',
1059
- cid: 'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a',
1060
- })
1061
- expect(() =>
1062
- lex.assertValidRecord('com.example.cid', {
1063
- $type: 'com.example.cid',
1064
- cid: 'abapsdofiuwrpoiasdfuaspdfoiu',
1065
- }),
1066
- ).toThrow('Record/cid must be a cid string')
1067
- })
1068
-
1069
- it('Applies language formatting constraint', () => {
1070
- lex.assertValidRecord('com.example.language', {
1071
- $type: 'com.example.language',
1072
- language: 'en-US-boont',
1073
- })
1074
- expect(() =>
1075
- lex.assertValidRecord('com.example.language', {
1076
- $type: 'com.example.language',
1077
- language: 'not-a-language-',
1078
- }),
1079
- ).toThrow('Record/language must be a well-formed BCP 47 language tag')
1080
- })
1081
-
1082
- it('Applies bytes length constraints', () => {
1083
- lex.assertValidRecord('com.example.byteLength', {
1084
- $type: 'com.example.byteLength',
1085
- bytes: new Uint8Array([1, 2, 3]),
1086
- })
1087
- expect(() =>
1088
- lex.assertValidRecord('com.example.byteLength', {
1089
- $type: 'com.example.byteLength',
1090
- bytes: new Uint8Array([1]),
1091
- }),
1092
- ).toThrow('Record/bytes must not be smaller than 2 bytes')
1093
- expect(() =>
1094
- lex.assertValidRecord('com.example.byteLength', {
1095
- $type: 'com.example.byteLength',
1096
- bytes: new Uint8Array([1, 2, 3, 4, 5]),
1097
- }),
1098
- ).toThrow('Record/bytes must not be larger than 4 bytes')
1099
- })
1100
- })
1101
-
1102
- describe('XRPC parameter validation', () => {
1103
- const lex = new Lexicons(LexiconDocs)
1104
-
1105
- it('Passes valid parameters', () => {
1106
- const queryResult = lex.assertValidXrpcParams('com.example.query', {
1107
- boolean: true,
1108
- integer: 123,
1109
- string: 'string',
1110
- array: ['x', 'y'],
1111
- })
1112
- expect(queryResult).toEqual({
1113
- boolean: true,
1114
- integer: 123,
1115
- string: 'string',
1116
- array: ['x', 'y'],
1117
- def: 0,
1118
- })
1119
- const paramResult = lex.assertValidXrpcParams('com.example.procedure', {
1120
- boolean: true,
1121
- integer: 123,
1122
- string: 'string',
1123
- array: ['x', 'y'],
1124
- def: 1,
1125
- })
1126
- expect(paramResult).toEqual({
1127
- boolean: true,
1128
- integer: 123,
1129
- string: 'string',
1130
- array: ['x', 'y'],
1131
- def: 1,
1132
- })
1133
- })
1134
-
1135
- it('Handles required correctly', () => {
1136
- lex.assertValidXrpcParams('com.example.query', {
1137
- boolean: true,
1138
- integer: 123,
1139
- })
1140
- expect(() =>
1141
- lex.assertValidXrpcParams('com.example.query', {
1142
- boolean: true,
1143
- }),
1144
- ).toThrow('Params must have the property "integer"')
1145
- expect(() =>
1146
- lex.assertValidXrpcParams('com.example.query', {
1147
- boolean: true,
1148
- integer: undefined,
1149
- }),
1150
- ).toThrow('Params must have the property "integer"')
1151
- })
1152
-
1153
- it('Validates parameter types', () => {
1154
- expect(() =>
1155
- lex.assertValidXrpcParams('com.example.query', {
1156
- boolean: 'string',
1157
- integer: 123,
1158
- string: 'string',
1159
- }),
1160
- ).toThrow('boolean must be a boolean')
1161
- expect(() =>
1162
- lex.assertValidXrpcParams('com.example.query', {
1163
- boolean: true,
1164
- float: 123.45,
1165
- integer: 123,
1166
- string: 'string',
1167
- array: 'x',
1168
- }),
1169
- ).toThrow('array must be an array')
1170
- })
1171
- })
1172
-
1173
- describe('XRPC input validation', () => {
1174
- const lex = new Lexicons(LexiconDocs)
1175
-
1176
- it('Passes valid inputs', () => {
1177
- lex.assertValidXrpcInput('com.example.procedure', {
1178
- object: { boolean: true },
1179
- array: ['one', 'two'],
1180
- boolean: true,
1181
- float: 123.45,
1182
- integer: 123,
1183
- string: 'string',
1184
- })
1185
- })
1186
-
1187
- it('Validates the input', () => {
1188
- // dont need to check this extensively since it's the same logic as tested in record validation
1189
- expect(() =>
1190
- lex.assertValidXrpcInput('com.example.procedure', {
1191
- object: { boolean: 'string' },
1192
- array: ['one', 'two'],
1193
- boolean: true,
1194
- float: 123.45,
1195
- integer: 123,
1196
- string: 'string',
1197
- }),
1198
- ).toThrow('Input/object/boolean must be a boolean')
1199
- expect(() => lex.assertValidXrpcInput('com.example.procedure', {})).toThrow(
1200
- 'Input must have the property "object"',
1201
- )
1202
- })
1203
- })
1204
-
1205
- describe('XRPC output validation', () => {
1206
- const lex = new Lexicons(LexiconDocs)
1207
-
1208
- it('Passes valid outputs', () => {
1209
- lex.assertValidXrpcOutput('com.example.query', {
1210
- object: { boolean: true },
1211
- array: ['one', 'two'],
1212
- boolean: true,
1213
- float: 123.45,
1214
- integer: 123,
1215
- string: 'string',
1216
- })
1217
- lex.assertValidXrpcOutput('com.example.procedure', {
1218
- object: { boolean: true },
1219
- array: ['one', 'two'],
1220
- boolean: true,
1221
- float: 123.45,
1222
- integer: 123,
1223
- string: 'string',
1224
- })
1225
- })
1226
-
1227
- it('Validates the output', () => {
1228
- // dont need to check this extensively since it's the same logic as tested in record validation
1229
- expect(() =>
1230
- lex.assertValidXrpcOutput('com.example.query', {
1231
- object: { boolean: 'string' },
1232
- array: ['one', 'two'],
1233
- boolean: true,
1234
- float: 123.45,
1235
- integer: 123,
1236
- string: 'string',
1237
- }),
1238
- ).toThrow('Output/object/boolean must be a boolean')
1239
- expect(() =>
1240
- lex.assertValidXrpcOutput('com.example.procedure', {}),
1241
- ).toThrow('Output must have the property "object"')
1242
- })
1243
- })