This commit is contained in:
j3d1 2026-08-21 21:37:12 +02:00
parent 7d9f67a77a
commit 3299c97392
13 changed files with 1407 additions and 327 deletions

View file

@ -0,0 +1,104 @@
import {encodeShortId, decodeShortId, deserializeShortId, serializeShortId} from '../short-id.js'
test('encodes the worked example from docs/handles-and-shortids.md', () => {
const token = encodeShortId([0, 7, 42])
expect(token).toBe('~DyU')
})
test('round-trips every kind as a flat [kindIndex, ...fieldValues] list', () => {
const cases = [
[0, 7, 42],
[1, 3, 1000],
[2, 0],
]
for (const ints of cases) {
const token = encodeShortId(ints)
expect(decodeShortId(token)).toEqual(ints)
}
})
test('round-trips small and large values, including zero', () => {
const values = [0, 1, 7, 42, 100, 10000, 100000, 123456789]
for (const value of values) {
const token = encodeShortId([2, value])
expect(decodeShortId(token)).toEqual([2, value])
}
})
test('tokens are URL-safe: only unreserved characters, plus the leading ~', () => {
const token = encodeShortId([0, 123456, 987654])
expect(token[0]).toBe('~')
expect(token.slice(1)).toMatch(/^[A-Za-z0-9\-_]+$/)
})
test('deserializeShortId names a decoded list into a flat shape, serializeShortId reverses it', () => {
const cases = [
[[0, 7, 42], {kind: 'item', owner_identity_id: 7, item_local_id: 42}],
[[1, 3, 1000], {kind: 'storage_location', owner_identity_id: 3, storage_location_id: 1000}],
[[2, 0], {kind: 'category', category_id: 0}],
[[3, 2, 9], {kind: 'workflow', owner_identity_id: 2, workflow_id: 9}],
[[4, 11], {kind: 'group', group_id: 11}],
[[5, 123], {kind: 'file', file_id: 123}],
]
for (const [ints, named] of cases) {
expect(deserializeShortId(ints)).toEqual(named)
expect(serializeShortId(named)).toEqual(ints)
}
})
test('kinds 3+ round-trip through the escape tag, not just the direct 0-2 range', () => {
for (const named of [
{kind: 'workflow', owner_identity_id: 2, workflow_id: 9},
{kind: 'group', group_id: 11},
{kind: 'file', file_id: 123},
]) {
const token = encodeShortId(serializeShortId(named))
expect(deserializeShortId(decodeShortId(token))).toEqual(named)
}
})
test('serializeShortId then deserializeShortId round-trips through encode/decode', () => {
const token = encodeShortId(serializeShortId({kind: 'item', owner_identity_id: 7, item_local_id: 42}))
expect(token).toBe('~DyU')
expect(deserializeShortId(decodeShortId(token))).toEqual({
kind: 'item',
owner_identity_id: 7,
item_local_id: 42
})
})
test('rejects a token missing the leading ~', () => {
expect(() => decodeShortId('DyU')).toThrow()
})
test('rejects an unknown kind index', () => {
expect(() => encodeShortId([99, 1])).toThrow()
})
test('deserializeShortId is keyed by (kind, fields.length), not kind alone', () => {
// kind=1 with the arity storage_location actually has (2 fields) is fine...
expect(deserializeShortId([1, 3, 1000])).toEqual({
kind: 'storage_location',
owner_identity_id: 3,
storage_location_id: 1000
})
// ...but kind=1 with a different arity (1 field) isn't a registered schema at all, and must
// not be silently misread as a truncated storage_location.
expect(() => deserializeShortId([1, 359])).toThrow()
})
test('rejects an unknown kind name', () => {
expect(() => serializeShortId({kind: 'not_a_kind'})).toThrow()
})
test('rejects a field count mismatch', () => {
expect(() => encodeShortId([0, 7])).toThrow()
})
test('rejects a missing field', () => {
expect(() => serializeShortId({kind: 'item', owner_identity_id: 7})).toThrow()
})
test('rejects a negative field value', () => {
expect(() => encodeShortId([2, -1])).toThrow()
})