@atproto/syntax 0.5.2 → 0.5.3

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @atproto/syntax
2
2
 
3
+ ## 0.5.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [#4815](https://github.com/bluesky-social/atproto/pull/4815) [`3711454`](https://github.com/bluesky-social/atproto/commit/371145432178b6c8c411f1289c266314cc7ec592) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Rewrite of tests
8
+
3
9
  ## 0.5.2
4
10
 
5
11
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/syntax",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "license": "MIT",
5
5
  "description": "Validation for atproto identifiers and formats: DID, handle, NSID, AT URI, etc",
6
6
  "keywords": [
@@ -0,0 +1,183 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { describe, expect, test } from 'vitest'
3
+ import { ensureValidAtUri, isValidAtUri } from '../src'
4
+
5
+ describe('valid interop', () => {
6
+ for (const value of readLines(
7
+ `${__dirname}/../../../interop-test-files/syntax/aturi_syntax_valid.txt`,
8
+ )) {
9
+ testValid(value)
10
+ }
11
+ })
12
+
13
+ // describe('invalid interop', () => {
14
+ // for (const value of readLines(
15
+ // `${__dirname}/../../../interop-test-files/syntax/aturi_syntax_invalid.txt`,
16
+ // )) {
17
+ // testInvalid(value)
18
+ // }
19
+ // })
20
+
21
+ describe('custom cases', () => {
22
+ describe('valid spec basics', () => {
23
+ testValid('at://did:plc:asdf123')
24
+ testValid('at://user.bsky.social')
25
+ testValid('at://did:plc:asdf123/com.atproto.feed.post')
26
+ testValid('at://did:plc:asdf123/com.atproto.feed.post/record')
27
+
28
+ testValid('at://did:plc:asdf123#/frag')
29
+ testValid('at://user.bsky.social#/frag')
30
+ testValid('at://did:plc:asdf123/com.atproto.feed.post#/frag')
31
+ testValid('at://did:plc:asdf123/com.atproto.feed.post/record#/frag')
32
+ })
33
+
34
+ describe('invalid spec basics', () => {
35
+ testInvalid('a://did:plc:asdf123')
36
+ testInvalid('at//did:plc:asdf123')
37
+ testInvalid('at:/a/did:plc:asdf123')
38
+ testInvalid('at:/did:plc:asdf123')
39
+ testInvalid('AT://did:plc:asdf123')
40
+ testInvalid('http://did:plc:asdf123')
41
+ testInvalid('://did:plc:asdf123')
42
+ testInvalid('at:did:plc:asdf123')
43
+ testInvalid('at:/did:plc:asdf123')
44
+ testInvalid('at:///did:plc:asdf123')
45
+ testInvalid('at://:/did:plc:asdf123')
46
+ testInvalid('at:/ /did:plc:asdf123')
47
+ testInvalid('at://did:plc:asdf123 ')
48
+ testInvalid('at://did:plc:asdf123/ ')
49
+ testInvalid(' at://did:plc:asdf123')
50
+ testInvalid('at://did:plc:asdf123/com.atproto.feed.post ')
51
+ testInvalid('at://did:plc:asdf123/com.atproto.feed.post# ')
52
+ testInvalid('at://did:plc:asdf123/com.atproto.feed.post#/ ')
53
+ testInvalid('at://did:plc:asdf123/com.atproto.feed.post#/frag ')
54
+ testInvalid('at://did:plc:asdf123/com.atproto.feed.post#fr ag')
55
+ testInvalid('//did:plc:asdf123')
56
+ testInvalid('at://name')
57
+ testInvalid('at://name.0')
58
+ testInvalid('at://diD:plc:asdf123')
59
+ testInvalid('at://did:plc:asdf123/com.atproto.feed.p@st')
60
+ testInvalid('at://did:plc:asdf123/com.atproto.feed.p$st')
61
+ testInvalid('at://did:plc:asdf123/com.atproto.feed.p%st')
62
+ testInvalid('at://did:plc:asdf123/com.atproto.feed.p&st')
63
+ testInvalid('at://did:plc:asdf123/com.atproto.feed.p()t')
64
+ testInvalid('at://did:plc:asdf123/com.atproto.feed_post')
65
+ testInvalid('at://did:plc:asdf123/-com.atproto.feed.post')
66
+ testInvalid('at://did:plc:asdf@123/com.atproto.feed.post')
67
+
68
+ testInvalid('at://DID:plc:asdf123')
69
+ testInvalid('at://user.bsky.123')
70
+ testInvalid('at://bsky')
71
+ testInvalid('at://did:plc:')
72
+ testInvalid('at://did:plc:')
73
+ testInvalid('at://frag')
74
+ })
75
+
76
+ describe('very long strings', () => {
77
+ testValid('at://did:plc:asdf123/com.atproto.feed.post/' + 'o'.repeat(800))
78
+ testInvalid(`at://did:plc:${'o'.repeat(8200)}/com.atproto.feed.post/record`)
79
+ })
80
+
81
+ describe('invalid collection', () => {
82
+ testInvalid('at://did:plc:asdf123/short/stuff')
83
+ testInvalid('at://did:plc:asdf123/12345')
84
+ })
85
+
86
+ describe('invalid repeated slashes', () => {
87
+ testInvalid('at://user.bsky.social//')
88
+ testInvalid('at://user.bsky.social//com.atproto.feed.post')
89
+ testInvalid('at://user.bsky.social/com.atproto.feed.post//')
90
+ })
91
+
92
+ describe('invalid trailing slashes', () => {
93
+ testInvalid('at://did:plc:asdf123/')
94
+ testInvalid('at://user.bsky.social/')
95
+ testInvalid('at://did:plc:asdf123/com.atproto.feed.post/')
96
+ testInvalid('at://did:plc:asdf123/com.atproto.feed.post/record/')
97
+ testInvalid('at://did:plc:asdf123/com.atproto.feed.post/record/#/frag')
98
+ })
99
+
100
+ describe('invalid segment count', () => {
101
+ testInvalid('at://did:plc:asdf123/com.atproto.feed.post/asdf123/asdf')
102
+ testInvalid('at://did:plc:asdf123/com.atproto.feed.post/asdf123/more/more')
103
+ })
104
+
105
+ describe('valid record key', () => {
106
+ testValid('at://did:plc:asdf123/com.atproto.feed.post/a')
107
+ testValid('at://did:plc:asdf123/com.atproto.feed.post/asdf123')
108
+ })
109
+
110
+ describe('invalid trailing slash', () => {
111
+ testInvalid('at://did:plc:asdf123/')
112
+ testInvalid('at://user.bsky.social/')
113
+ testInvalid('at://did:plc:asdf123/com.atproto.feed.post/')
114
+ testInvalid('at://did:plc:asdf123/com.atproto.feed.post/record/')
115
+ testInvalid('at://did:plc:asdf123/com.atproto.feed.post/record/#/frag')
116
+ })
117
+
118
+ describe('invalid record keys', () => {
119
+ // is probably too permissive about URL encoding
120
+ testValid('at://did:plc:asdf123/com.atproto.feed.post/%30')
121
+ testValid('at://did:plc:asdf123/com.atproto.feed.post/%3')
122
+ testValid('at://did:plc:asdf123/com.atproto.feed.post/%')
123
+ testValid('at://did:plc:asdf123/com.atproto.feed.post/%zz')
124
+ testValid('at://did:plc:asdf123/com.atproto.feed.post/%%%')
125
+
126
+ // is very permissive about fragments
127
+ testValid('at://did:plc:asdf123/com.atproto.feed.post/%23')
128
+
129
+ testValid('at://did:plc:asdf123/com.atproto.feed.post/$@!*)(:,;~.sdf123')
130
+ testValid("at://did:plc:asdf123/com.atproto.feed.post/~'sdf123")
131
+
132
+ testValid('at://did:plc:asdf123/com.atproto.feed.post/$')
133
+ testValid('at://did:plc:asdf123/com.atproto.feed.post/@')
134
+ testValid('at://did:plc:asdf123/com.atproto.feed.post/!')
135
+ testValid('at://did:plc:asdf123/com.atproto.feed.post/*')
136
+ testValid('at://did:plc:asdf123/com.atproto.feed.post/(')
137
+ testValid('at://did:plc:asdf123/com.atproto.feed.post/,')
138
+ testValid('at://did:plc:asdf123/com.atproto.feed.post/;')
139
+ testValid('at://did:plc:asdf123/com.atproto.feed.post/abc%30123')
140
+ })
141
+
142
+ describe('valid fragment', () => {
143
+ testValid('at://did:plc:asdf123#/frac')
144
+ })
145
+
146
+ describe('invalid fragment', () => {
147
+ testValid('at://did:plc:asdf123#/com.atproto.feed.post')
148
+ testValid('at://did:plc:asdf123#/com.atproto.feed.post/')
149
+ testValid('at://did:plc:asdf123#/asdf/')
150
+
151
+ testValid('at://did:plc:asdf123/com.atproto.feed.post#/$@!*():,;~.sdf123')
152
+ testValid('at://did:plc:asdf123#/[asfd]')
153
+
154
+ testValid('at://did:plc:asdf123#/$')
155
+ testValid('at://did:plc:asdf123#/*')
156
+ testValid('at://did:plc:asdf123#/;')
157
+ testValid('at://did:plc:asdf123#/,')
158
+
159
+ testInvalid('at://did:plc:asdf123#')
160
+ testInvalid('at://did:plc:asdf123##')
161
+ testInvalid('#at://did:plc:asdf123')
162
+ testInvalid('at://did:plc:asdf123#/asdf#/asdf')
163
+ })
164
+ })
165
+
166
+ function testValid(value: string) {
167
+ test(value, () => {
168
+ expect(isValidAtUri(value)).toBe(true)
169
+ expect(() => ensureValidAtUri(value)).not.toThrow()
170
+ })
171
+ }
172
+
173
+ function testInvalid(value: string) {
174
+ test(value, () => {
175
+ expect(isValidAtUri(value)).toBe(false)
176
+ })
177
+ }
178
+
179
+ function readLines(filePath: string): string[] {
180
+ return readFileSync(filePath, 'utf-8')
181
+ .split(/\r?\n/)
182
+ .filter((line) => !line.startsWith('#') && line.length > 0)
183
+ }
@@ -1,12 +1,26 @@
1
- import * as fs from 'node:fs'
2
- import * as readline from 'node:readline'
3
- import { describe, expect, it } from 'vitest'
4
- import { AtUri, ensureValidAtUri, ensureValidAtUriRegex } from '../src'
1
+ import { readFileSync } from 'node:fs'
2
+ import { describe, expect, it, test } from 'vitest'
3
+ import { AtUri } from '../src'
5
4
 
6
5
  describe(AtUri, () => {
7
- it('parses valid at uris', () => {
8
- // input host path query hash
9
- type AtUriTest = [string, string, string, string, string]
6
+ describe('parses valid interop', () => {
7
+ for (const value of readLines(
8
+ `${__dirname}/../../../interop-test-files/syntax/aturi_syntax_valid.txt`,
9
+ )) {
10
+ test(value, () => {
11
+ expect(() => new AtUri(value)).not.toThrow()
12
+ })
13
+ }
14
+ })
15
+
16
+ describe('valid at uris', () => {
17
+ type AtUriTest = [
18
+ input: string,
19
+ host: string,
20
+ path: string,
21
+ query: string,
22
+ hash: string,
23
+ ]
10
24
  const TESTS: AtUriTest[] = [
11
25
  ['foo.com', 'foo.com', '', '', ''],
12
26
  ['at://foo.com', 'foo.com', '', '', ''],
@@ -246,15 +260,17 @@ describe(AtUri, () => {
246
260
  '',
247
261
  ],
248
262
  ]
249
- for (const [uri, hostname, pathname, search, hash] of TESTS) {
250
- const urip = new AtUri(uri)
251
- expect(urip.protocol).toBe('at:')
252
- expect(urip.host).toBe(hostname)
253
- expect(urip.hostname).toBe(hostname)
254
- expect(urip.origin).toBe(`at://${hostname}`)
255
- expect(urip.pathname).toBe(pathname)
256
- expect(urip.search).toBe(search)
257
- expect(urip.hash).toBe(hash)
263
+ for (const [input, host, path, search, hash] of TESTS) {
264
+ test(input, () => {
265
+ const urip = new AtUri(input)
266
+ expect(urip.protocol).toBe('at:')
267
+ expect(urip.host).toBe(host)
268
+ expect(urip.hostname).toBe(host)
269
+ expect(urip.origin).toBe(`at://${host}`)
270
+ expect(urip.pathname).toBe(path)
271
+ expect(urip.search).toBe(search)
272
+ expect(urip.hash).toBe(hash)
273
+ })
258
274
  }
259
275
  })
260
276
 
@@ -316,11 +332,9 @@ describe(AtUri, () => {
316
332
  expect(urip.toString()).toBe('at://foo.com/foo?foo=bar&baz=buux#hash')
317
333
  })
318
334
 
319
- it('supports relative URIs', () => {
320
- // input path query hash
321
- type AtUriTest = [string, string, string, string]
335
+ describe('relative URIs', () => {
336
+ type AtUriTest = [input: string, path: string, search: string, hash: string]
322
337
  const TESTS: AtUriTest[] = [
323
- // input hostname pathname query hash
324
338
  ['', '', '', ''],
325
339
  ['/', '/', '', ''],
326
340
  ['/foo', '/foo', '', ''],
@@ -347,180 +361,23 @@ describe(AtUri, () => {
347
361
  ]
348
362
 
349
363
  for (const base of BASES) {
350
- const basep = new AtUri(base)
351
- for (const [relative, pathname, search, hash] of TESTS) {
352
- const urip = new AtUri(relative, base)
353
- expect(urip.protocol).toBe('at:')
354
- expect(urip.host).toBe(basep.host)
355
- expect(urip.hostname).toBe(basep.hostname)
356
- expect(urip.origin).toBe(basep.origin)
357
- expect(urip.pathname).toBe(pathname)
358
- expect(urip.search).toBe(search)
359
- expect(urip.hash).toBe(hash)
360
- }
364
+ describe(base, () => {
365
+ for (const [input, path, search, hash] of TESTS) {
366
+ test(input, () => {
367
+ const baseUri = new AtUri(base)
368
+ const uri = new AtUri(input, base)
369
+ expect(uri.protocol).toBe('at:')
370
+ expect(uri.host).toBe(baseUri.host)
371
+ expect(uri.hostname).toBe(baseUri.hostname)
372
+ expect(uri.origin).toBe(baseUri.origin)
373
+ expect(uri.pathname).toBe(path)
374
+ expect(uri.search).toBe(search)
375
+ expect(uri.hash).toBe(hash)
376
+ })
377
+ }
378
+ })
361
379
  }
362
380
  })
363
- })
364
-
365
- describe('AtUri validation', () => {
366
- const expectValid = (h: string) => {
367
- ensureValidAtUri(h)
368
- ensureValidAtUriRegex(h)
369
- }
370
- const expectInvalid = (h: string) => {
371
- expect(() => ensureValidAtUri(h)).toThrow()
372
- expect(() => ensureValidAtUriRegex(h)).toThrow()
373
- }
374
-
375
- it('enforces spec basics', () => {
376
- expectValid('at://did:plc:asdf123')
377
- expectValid('at://user.bsky.social')
378
- expectValid('at://did:plc:asdf123/com.atproto.feed.post')
379
- expectValid('at://did:plc:asdf123/com.atproto.feed.post/record')
380
-
381
- expectValid('at://did:plc:asdf123#/frag')
382
- expectValid('at://user.bsky.social#/frag')
383
- expectValid('at://did:plc:asdf123/com.atproto.feed.post#/frag')
384
- expectValid('at://did:plc:asdf123/com.atproto.feed.post/record#/frag')
385
-
386
- expectInvalid('a://did:plc:asdf123')
387
- expectInvalid('at//did:plc:asdf123')
388
- expectInvalid('at:/a/did:plc:asdf123')
389
- expectInvalid('at:/did:plc:asdf123')
390
- expectInvalid('AT://did:plc:asdf123')
391
- expectInvalid('http://did:plc:asdf123')
392
- expectInvalid('://did:plc:asdf123')
393
- expectInvalid('at:did:plc:asdf123')
394
- expectInvalid('at:/did:plc:asdf123')
395
- expectInvalid('at:///did:plc:asdf123')
396
- expectInvalid('at://:/did:plc:asdf123')
397
- expectInvalid('at:/ /did:plc:asdf123')
398
- expectInvalid('at://did:plc:asdf123 ')
399
- expectInvalid('at://did:plc:asdf123/ ')
400
- expectInvalid(' at://did:plc:asdf123')
401
- expectInvalid('at://did:plc:asdf123/com.atproto.feed.post ')
402
- expectInvalid('at://did:plc:asdf123/com.atproto.feed.post# ')
403
- expectInvalid('at://did:plc:asdf123/com.atproto.feed.post#/ ')
404
- expectInvalid('at://did:plc:asdf123/com.atproto.feed.post#/frag ')
405
- expectInvalid('at://did:plc:asdf123/com.atproto.feed.post#fr ag')
406
- expectInvalid('//did:plc:asdf123')
407
- expectInvalid('at://name')
408
- expectInvalid('at://name.0')
409
- expectInvalid('at://diD:plc:asdf123')
410
- expectInvalid('at://did:plc:asdf123/com.atproto.feed.p@st')
411
- expectInvalid('at://did:plc:asdf123/com.atproto.feed.p$st')
412
- expectInvalid('at://did:plc:asdf123/com.atproto.feed.p%st')
413
- expectInvalid('at://did:plc:asdf123/com.atproto.feed.p&st')
414
- expectInvalid('at://did:plc:asdf123/com.atproto.feed.p()t')
415
- expectInvalid('at://did:plc:asdf123/com.atproto.feed_post')
416
- expectInvalid('at://did:plc:asdf123/-com.atproto.feed.post')
417
- expectInvalid('at://did:plc:asdf@123/com.atproto.feed.post')
418
-
419
- expectInvalid('at://DID:plc:asdf123')
420
- expectInvalid('at://user.bsky.123')
421
- expectInvalid('at://bsky')
422
- expectInvalid('at://did:plc:')
423
- expectInvalid('at://did:plc:')
424
- expectInvalid('at://frag')
425
-
426
- expectValid('at://did:plc:asdf123/com.atproto.feed.post/' + 'o'.repeat(800))
427
- expectInvalid(
428
- 'at://did:plc:asdf123/com.atproto.feed.post/' + 'o'.repeat(8200),
429
- )
430
- })
431
-
432
- it('has specified behavior on edge cases', () => {
433
- expectInvalid('at://user.bsky.social//')
434
- expectInvalid('at://user.bsky.social//com.atproto.feed.post')
435
- expectInvalid('at://user.bsky.social/com.atproto.feed.post//')
436
- expectInvalid(
437
- 'at://did:plc:asdf123/com.atproto.feed.post/asdf123/more/more',
438
- )
439
- expectInvalid('at://did:plc:asdf123/short/stuff')
440
- expectInvalid('at://did:plc:asdf123/12345')
441
- })
442
-
443
- it('enforces no trailing slashes', () => {
444
- expectValid('at://did:plc:asdf123')
445
- expectInvalid('at://did:plc:asdf123/')
446
-
447
- expectValid('at://user.bsky.social')
448
- expectInvalid('at://user.bsky.social/')
449
-
450
- expectValid('at://did:plc:asdf123/com.atproto.feed.post')
451
- expectInvalid('at://did:plc:asdf123/com.atproto.feed.post/')
452
-
453
- expectValid('at://did:plc:asdf123/com.atproto.feed.post/record')
454
- expectInvalid('at://did:plc:asdf123/com.atproto.feed.post/record/')
455
- expectInvalid('at://did:plc:asdf123/com.atproto.feed.post/record/#/frag')
456
- })
457
-
458
- it('enforces strict paths', () => {
459
- expectValid('at://did:plc:asdf123/com.atproto.feed.post/asdf123')
460
- expectInvalid('at://did:plc:asdf123/com.atproto.feed.post/asdf123/asdf')
461
- })
462
-
463
- it('is very permissive about record keys', () => {
464
- expectValid('at://did:plc:asdf123/com.atproto.feed.post/asdf123')
465
- expectValid('at://did:plc:asdf123/com.atproto.feed.post/a')
466
- expectValid('at://did:plc:asdf123/com.atproto.feed.post/%23')
467
-
468
- expectValid('at://did:plc:asdf123/com.atproto.feed.post/$@!*)(:,;~.sdf123')
469
- expectValid("at://did:plc:asdf123/com.atproto.feed.post/~'sdf123")
470
-
471
- expectValid('at://did:plc:asdf123/com.atproto.feed.post/$')
472
- expectValid('at://did:plc:asdf123/com.atproto.feed.post/@')
473
- expectValid('at://did:plc:asdf123/com.atproto.feed.post/!')
474
- expectValid('at://did:plc:asdf123/com.atproto.feed.post/*')
475
- expectValid('at://did:plc:asdf123/com.atproto.feed.post/(')
476
- expectValid('at://did:plc:asdf123/com.atproto.feed.post/,')
477
- expectValid('at://did:plc:asdf123/com.atproto.feed.post/;')
478
- expectValid('at://did:plc:asdf123/com.atproto.feed.post/abc%30123')
479
- })
480
-
481
- it('is probably too permissive about URL encoding', () => {
482
- expectValid('at://did:plc:asdf123/com.atproto.feed.post/%30')
483
- expectValid('at://did:plc:asdf123/com.atproto.feed.post/%3')
484
- expectValid('at://did:plc:asdf123/com.atproto.feed.post/%')
485
- expectValid('at://did:plc:asdf123/com.atproto.feed.post/%zz')
486
- expectValid('at://did:plc:asdf123/com.atproto.feed.post/%%%')
487
- })
488
-
489
- it('is very permissive about fragments', () => {
490
- expectValid('at://did:plc:asdf123#/frac')
491
-
492
- expectInvalid('at://did:plc:asdf123#')
493
- expectInvalid('at://did:plc:asdf123##')
494
- expectInvalid('#at://did:plc:asdf123')
495
- expectInvalid('at://did:plc:asdf123#/asdf#/asdf')
496
-
497
- expectValid('at://did:plc:asdf123#/com.atproto.feed.post')
498
- expectValid('at://did:plc:asdf123#/com.atproto.feed.post/')
499
- expectValid('at://did:plc:asdf123#/asdf/')
500
-
501
- expectValid('at://did:plc:asdf123/com.atproto.feed.post#/$@!*():,;~.sdf123')
502
- expectValid('at://did:plc:asdf123#/[asfd]')
503
-
504
- expectValid('at://did:plc:asdf123#/$')
505
- expectValid('at://did:plc:asdf123#/*')
506
- expectValid('at://did:plc:asdf123#/;')
507
- expectValid('at://did:plc:asdf123#/,')
508
- })
509
-
510
- it('conforms to interop valid ATURIs', () => {
511
- const lineReader = readline.createInterface({
512
- input: fs.createReadStream(
513
- `${__dirname}/interop-files/aturi_syntax_valid.txt`,
514
- ),
515
- terminal: false,
516
- })
517
- lineReader.on('line', (line) => {
518
- if (line.startsWith('#') || line.length === 0) {
519
- return
520
- }
521
- expectValid(line)
522
- })
523
- })
524
381
 
525
382
  it('properly checks that the did property is a valid did', () => {
526
383
  const urip = new AtUri('at://did:example:123')
@@ -568,6 +425,10 @@ describe('AtUri validation', () => {
568
425
  expect(urip.rkey).toBe('not a valid rkey')
569
426
  expect(() => urip.rkeySafe).toThrow()
570
427
  })
571
-
572
- // NOTE: this package is currently more permissive than spec about AT URIs, so invalid cases are not errors
573
428
  })
429
+
430
+ function readLines(filePath: string): string[] {
431
+ return readFileSync(filePath, 'utf-8')
432
+ .split(/\r?\n/)
433
+ .filter((line) => !line.startsWith('#') && line.length > 0)
434
+ }