Files
atproto/packages/syntax/tests/aturi-string.test.ts
Matthieu Sieben 26d793af95 Improve AtUriString type an utilities (#4806)
* Improve `AtUriString` type an utilities

* review comments

* tidy

* tidy

* tidy

* tidy

* Apply suggestion from @matthieusieben

* Apply suggestion from @matthieusieben

* review comments
2026-04-14 18:57:10 +02:00

212 lines
8.1 KiB
TypeScript

import { readFileSync } from 'node:fs'
import { describe, expect, test } from 'vitest'
import { InvalidAtUriError, assertAtUriString, isAtUriString } from '../src'
describe('valid interop', () => {
for (const value of readLines(
`${__dirname}/../../../interop-test-files/syntax/aturi_syntax_valid.txt`,
)) {
testValid(value)
}
})
describe('invalid interop', () => {
for (const value of readLines(
`${__dirname}/../../../interop-test-files/syntax/aturi_syntax_invalid.txt`,
)) {
testInvalid(value)
}
})
describe('custom cases', () => {
describe('valid spec basics', () => {
testValid('at://did:plc:asdf123')
testValid('at://user.bsky.social')
testValid('at://did:plc:asdf123/com.atproto.feed.post')
testValid('at://did:plc:asdf123/com.atproto.feed.post/record')
testValid('at://did:plc:asdf123#/frag')
testValid('at://user.bsky.social#/frag')
testValid('at://did:plc:asdf123/com.atproto.feed.post#/frag')
testValid('at://did:plc:asdf123/com.atproto.feed.post/record#/frag')
})
describe('invalid spec basics', () => {
testInvalid('a://did:plc:asdf123')
testInvalid('at//did:plc:asdf123')
testInvalid('at:/a/did:plc:asdf123')
testInvalid('at:/did:plc:asdf123')
testInvalid('AT://did:plc:asdf123')
testInvalid('http://did:plc:asdf123')
testInvalid('://did:plc:asdf123')
testInvalid('at:did:plc:asdf123')
testInvalid('at:/did:plc:asdf123')
testInvalid('at:///did:plc:asdf123')
testInvalid('at://:/did:plc:asdf123')
testInvalid('at:/ /did:plc:asdf123')
testInvalid('at://did:plc:asdf123 ')
testInvalid('at://did:plc:asdf123/ ')
testInvalid(' at://did:plc:asdf123')
testInvalid('at://did:plc:asdf123/com.atproto.feed.post ')
testInvalid('at://did:plc:asdf123/com.atproto.feed.post# ')
testInvalid('at://did:plc:asdf123/com.atproto.feed.post#/ ')
testInvalid('at://did:plc:asdf123/com.atproto.feed.post#/frag ')
testInvalid('at://did:plc:asdf123/com.atproto.feed.post#fr ag')
testInvalid('//did:plc:asdf123')
testInvalid('at://name')
testInvalid('at://name.0')
testInvalid('at://diD:plc:asdf123')
testInvalid('at://did:plc:asdf123/com.atproto.feed.p@st')
testInvalid('at://did:plc:asdf123/com.atproto.feed.p$st')
testInvalid('at://did:plc:asdf123/com.atproto.feed.p%st')
testInvalid('at://did:plc:asdf123/com.atproto.feed.p&st')
testInvalid('at://did:plc:asdf123/com.atproto.feed.p()t')
testInvalid('at://did:plc:asdf123/com.atproto.feed_post')
testInvalid('at://did:plc:asdf123/-com.atproto.feed.post')
testInvalid('at://did:plc:asdf@123/com.atproto.feed.post')
testInvalid('at://did:plc:asdf123?a')
testInvalid('at://user.bsky.social?a=B')
testInvalid('at://did:plc:asdf123/com.atproto.feed.post?foo=bar')
testInvalid('at://did:plc:asdf123/com.atproto.feed.post/record?q=3')
testInvalid('at://did:plc:asdf123?a=b#/frag')
testInvalid('at://user.bsky.social?a=b#/frag')
testInvalid('at://did:plc:asdf123/com.atproto.feed.post?a=b#/frag')
testInvalid('at://did:plc:asdf123/com.atproto.feed.post/record?a=b#/frag')
testInvalid('at://DID:plc:asdf123')
testInvalid('at://user.bsky.123')
testInvalid('at://bsky')
testInvalid('at://did:plc:')
testInvalid('at://did:plc:')
testInvalid('at://frag')
})
describe('very long strings', () => {
testValid('at://did:plc:asdf123/com.atproto.feed.post/' + 'o'.repeat(512))
testValid(`at://did:web:x${'.y'.repeat(100)}/com.atproto.feed.post/record`)
testInvalid(`at://did:plc:${'o'.repeat(8200)}/com.atproto.feed.post/record`)
testInvalid('at://did:plc:asdf123/com.atproto.feed.post/' + 'o'.repeat(513))
testInvalid('at://did:plc:asdf123/com.atproto.feed.post/' + 'o'.repeat(800))
})
describe('invalid collection', () => {
testInvalid('at://did:plc:asdf123/short/stuff')
testInvalid('at://did:plc:asdf123/12345')
})
describe('invalid repeated slashes', () => {
testInvalid('at://user.bsky.social//')
testInvalid('at://user.bsky.social//com.atproto.feed.post')
testInvalid('at://user.bsky.social/com.atproto.feed.post//')
})
describe('invalid trailing slashes', () => {
testInvalid('at://did:plc:asdf123/')
testInvalid('at://user.bsky.social/')
testInvalid('at://did:plc:asdf123/com.atproto.feed.post/')
testInvalid('at://did:plc:asdf123/com.atproto.feed.post/record/')
testInvalid('at://did:plc:asdf123/com.atproto.feed.post/record/#/frag')
})
describe('invalid segment count', () => {
testInvalid('at://did:plc:asdf123/com.atproto.feed.post/asdf123/asdf')
testInvalid('at://did:plc:asdf123/com.atproto.feed.post/asdf123/more/more')
})
describe('valid record key', () => {
testValid('at://did:plc:asdf123/com.atproto.feed.post/a')
testValid('at://did:plc:asdf123/com.atproto.feed.post/asdf123')
})
describe('loosely valid trailing slash', () => {
testLoose('at://did:plc:asdf123/')
testLoose('at://user.bsky.social/')
testLoose('at://did:plc:asdf123/com.atproto.feed.post/')
testLoose('at://did:plc:asdf123/com.atproto.feed.post/record/')
testLoose('at://did:plc:asdf123/com.atproto.feed.post/record/#/frag')
})
describe('loosely valid record keys', () => {
testLoose('at://did:plc:asdf123/com.atproto.feed.post/%23')
testLoose('at://did:plc:asdf123/com.atproto.feed.post/$@!*)(:,;~.sdf123')
testLoose("at://did:plc:asdf123/com.atproto.feed.post/~'sdf123")
testLoose('at://did:plc:asdf123/com.atproto.feed.post/$')
testLoose('at://did:plc:asdf123/com.atproto.feed.post/@')
testLoose('at://did:plc:asdf123/com.atproto.feed.post/!')
testLoose('at://did:plc:asdf123/com.atproto.feed.post/*')
testLoose('at://did:plc:asdf123/com.atproto.feed.post/(')
testLoose('at://did:plc:asdf123/com.atproto.feed.post/,')
testLoose('at://did:plc:asdf123/com.atproto.feed.post/;')
testLoose('at://did:plc:asdf123/com.atproto.feed.post/abc%30123')
testLoose('at://did:plc:asdf123/com.atproto.feed.post/%30')
testLoose('at://did:plc:asdf123/com.atproto.feed.post/%3')
testLoose('at://did:plc:asdf123/com.atproto.feed.post/%')
testLoose('at://did:plc:asdf123/com.atproto.feed.post/%zz')
testLoose('at://did:plc:asdf123/com.atproto.feed.post/%%%')
testLoose('at://did:plc:asdf123/com.atproto.feed.post/[]')
testLoose('at://did:plc:asdf123/com.atproto.feed.post/foo[')
testLoose('at://did:plc:asdf123/com.atproto.feed.post/bar]')
testLoose('at://did:plc:asdf123/com.atproto.feed.post/[baz]')
})
describe('valid fragment', () => {
testValid('at://did:plc:asdf123#/frac')
})
describe('invalid fragment', () => {
testValid('at://did:plc:asdf123#/com.atproto.feed.post')
testValid('at://did:plc:asdf123#/com.atproto.feed.post/')
testValid('at://did:plc:asdf123#/asdf/')
testValid('at://did:plc:asdf123/com.atproto.feed.post#/$@!*():,;~.sdf123')
testValid('at://did:plc:asdf123#/[asfd]')
testValid('at://did:plc:asdf123#/$')
testValid('at://did:plc:asdf123#/*')
testValid('at://did:plc:asdf123#/;')
testValid('at://did:plc:asdf123#/,')
testInvalid('at://did:plc:asdf123#')
testInvalid('at://did:plc:asdf123##')
testInvalid('#at://did:plc:asdf123')
testInvalid('at://did:plc:asdf123#/asdf#/asdf')
})
})
function testValid(value: string) {
test(value, () => {
expect(isAtUriString(value)).toBe(true)
expect(isAtUriString(value, { strict: false })).toBe(true)
expect(() => assertAtUriString(value)).not.toThrow()
expect(() => assertAtUriString(value, { strict: false })).not.toThrow()
})
}
function testInvalid(value: string) {
test(value, () => {
expect(isAtUriString(value)).toBe(false)
expect(() => assertAtUriString(value)).toThrow(InvalidAtUriError)
})
}
function testLoose(value: string) {
test(value, () => {
expect(isAtUriString(value)).toBe(false)
expect(isAtUriString(value, { strict: false })).toBe(true)
expect(() => assertAtUriString(value)).toThrow()
expect(() => assertAtUriString(value, { strict: false })).not.toThrow()
})
}
function readLines(filePath: string): string[] {
return readFileSync(filePath, 'utf-8')
.split(/\r?\n/)
.filter((line) => !line.startsWith('#') && line.length > 0)
}