stash
This commit is contained in:
parent
3299c97392
commit
de7d9426be
10 changed files with 259 additions and 99 deletions
|
|
@ -5,23 +5,60 @@ test('encodes the worked example from docs/handles-and-shortids.md', () => {
|
|||
expect(token).toBe('~DyU')
|
||||
})
|
||||
|
||||
test('round-trips every kind as a flat [kindIndex, ...fieldValues] list', () => {
|
||||
test('kind ids are scoped per arity: id 0 means item (arity 2) or category (arity 1)', () => {
|
||||
expect(deserializeShortId(decodeShortId(encodeShortId([0, 7, 42])))).toEqual({
|
||||
kind: 'item', owner_identity_id: 7, item_local_id: 42
|
||||
})
|
||||
expect(deserializeShortId(decodeShortId(encodeShortId([0, 5])))).toEqual({
|
||||
kind: 'category', category_id: 5
|
||||
})
|
||||
})
|
||||
|
||||
test('every kind round-trips through serialize/encode/decode/deserialize', () => {
|
||||
const cases = [
|
||||
[0, 7, 42],
|
||||
[1, 3, 1000],
|
||||
[2, 0],
|
||||
{kind: 'item', owner_identity_id: 7, item_local_id: 42},
|
||||
{kind: 'category', category_id: 5},
|
||||
{kind: 'group_item', owner_group_id: 5, item_local_id: 42},
|
||||
{kind: 'group', group_id: 11},
|
||||
{kind: 'storage_location', owner_identity_id: 3, storage_location_id: 1000},
|
||||
{kind: 'file', file_id: 123},
|
||||
{kind: 'workflow', owner_identity_id: 2, workflow_id: 9},
|
||||
]
|
||||
for (const ints of cases) {
|
||||
const token = encodeShortId(ints)
|
||||
expect(decodeShortId(token)).toEqual(ints)
|
||||
for (const named of cases) {
|
||||
const token = encodeShortId(serializeShortId(named))
|
||||
expect(deserializeShortId(decodeShortId(token))).toEqual(named)
|
||||
}
|
||||
})
|
||||
|
||||
test('round-trips small and large values, including zero', () => {
|
||||
const values = [0, 1, 7, 42, 100, 10000, 100000, 123456789]
|
||||
test('a non-last field may be 0 - only the last field is restricted', () => {
|
||||
const named = {kind: 'item', owner_identity_id: 0, item_local_id: 42}
|
||||
const token = encodeShortId(serializeShortId(named))
|
||||
expect(deserializeShortId(decodeShortId(token))).toEqual(named)
|
||||
})
|
||||
|
||||
test('rejects a 0 last-field value, for a single-field kind', () => {
|
||||
expect(() => encodeShortId(serializeShortId({kind: 'category', category_id: 0}))).toThrow()
|
||||
})
|
||||
|
||||
test('rejects a 0 last-field value, for a multi-field kind', () => {
|
||||
expect(() => encodeShortId(serializeShortId({kind: 'item', owner_identity_id: 7, item_local_id: 0}))).toThrow()
|
||||
})
|
||||
|
||||
test('decoding never over-reads: a single-chunk field that exactly exhausts padding is not ' +
|
||||
'mistaken for a second field', () => {
|
||||
// category_id: 5 encodes as kind-tag(2 bits) + one 5-bit chunk = 7 bits, padded with exactly
|
||||
// 5 zero bits - the same 5 bits as a genuine one-chunk field of value 0. This is the concrete
|
||||
// case the last-field-nonzero rule exists to disambiguate.
|
||||
const token = encodeShortId([0, 5])
|
||||
expect(token).toBe('~Cg')
|
||||
expect(decodeShortId(token)).toEqual([0, 5])
|
||||
})
|
||||
|
||||
test('round-trips small and large nonzero values, in both last and non-last field position', () => {
|
||||
const values = [1, 7, 42, 100, 10000, 100000, 123456789]
|
||||
for (const value of values) {
|
||||
const token = encodeShortId([2, value])
|
||||
expect(decodeShortId(token)).toEqual([2, value])
|
||||
expect(decodeShortId(encodeShortId([0, value]))).toEqual([0, value])
|
||||
expect(decodeShortId(encodeShortId([0, value, 1]))).toEqual([0, value, 1])
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -31,30 +68,11 @@ test('tokens are URL-safe: only unreserved characters, plus the leading ~', () =
|
|||
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('kinds at escaped ids (3+) round-trip through the escape tag, not just the direct 0-2 range', () => {
|
||||
const token = encodeShortId(serializeShortId({kind: 'workflow', owner_identity_id: 2, workflow_id: 9}))
|
||||
expect(deserializeShortId(decodeShortId(token))).toEqual({
|
||||
kind: 'workflow', owner_identity_id: 2, workflow_id: 9
|
||||
})
|
||||
})
|
||||
|
||||
test('serializeShortId then deserializeShortId round-trips through encode/decode', () => {
|
||||
|
|
@ -75,30 +93,19 @@ 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 arity with no registered schema at that kind index', () => {
|
||||
// kind 0 is registered for arity 2 (item) and arity 1 (category), but not arity 3.
|
||||
expect(() => encodeShortId([0, 7, 8, 9])).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()
|
||||
expect(() => encodeShortId([0, -1, 42])).toThrow()
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue