Skip to content

Commit

Permalink
fix: v1 tests
Browse files Browse the repository at this point in the history
  • Loading branch information
broofa committed Jul 18, 2024
1 parent 62d6734 commit a33ff8d
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 205 deletions.
35 changes: 0 additions & 35 deletions src/test/v1-random.test.ts

This file was deleted.

252 changes: 128 additions & 124 deletions src/test/v1.test.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,56 @@
import * as assert from 'assert';
import test, { describe } from 'node:test';
import v1 from '../v1.js';
import parse from '../parse.js';
import v1, { updateV1State } from '../v1.js';

// Verify ordering of v1 ids created with explicit times
const TIME = 1321644961388; // 2011-11-18 11:36:01.388-08:00

// Fixture values for testing with the rfc v7 UUID example:
// https://www.rfc-editor.org/rfc/rfc9562.html#name-example-of-a-uuidv1-value
const RFC_V1 = 'c232ab00-9414-11ec-b3c8-9f68deced846';
const RFC_V1_BYTES = parse(RFC_V1);

// `options` for producing the above RFC UUID
const RFC_OPTIONS = {
msecs: 0x17f22e279b0,
nsecs: 0,
clockseq: 0x33c8,
node: Uint8Array.of(0x9f, 0x68, 0xde, 0xce, 0xd8, 0x46),
};

// random bytes for producing the above RFC UUID
const RFC_RANDOM = Uint8Array.of(
// unused
0,
0,
0,
0,
0,
0,
0,
0,

// clock seq
RFC_OPTIONS.clockseq >> 8,
RFC_OPTIONS.clockseq & 0xff,

// node
...RFC_OPTIONS.node
);

// Compare v1 timestamp fields chronologically
function compareV1TimeField(a: string, b: string) {
a = a.split('-').slice(0, 3).reverse().join('');
b = b.split('-').slice(0, 3).reverse().join('');
return a < b ? -1 : a > b ? 1 : 0;
}

describe('v1', () => {
test('v1 sort order (default)', () => {
const ids = [v1(), v1(), v1(), v1(), v1()];

const sorted = [...ids].sort((a, b) => {
a = a.split('-').reverse().join('-');
b = b.split('-').reverse().join('-');
return a < b ? -1 : a > b ? 1 : 0;
});

const sorted = [...ids].sort(compareV1TimeField);
assert.deepEqual(ids, sorted);
});

Expand All @@ -28,140 +64,108 @@ describe('v1', () => {
v1({ msecs: TIME + 28 * 24 * 3600 * 1000 }),
];

const sorted = [...ids].sort((a, b) => {
a = a.split('-').reverse().join('-');
b = b.split('-').reverse().join('-');
return a < b ? -1 : a > b ? 1 : 0;
});

const sorted = [...ids].sort(compareV1TimeField);
assert.deepEqual(ids, sorted);
});

test('msec', () => {
assert.ok(
v1({ msecs: TIME }) !== v1({ msecs: TIME }),
'IDs created at same msec are different'
);
test('v1(options)', () => {
assert.equal(v1({ msecs: RFC_OPTIONS.msecs, random: RFC_RANDOM }), RFC_V1, 'minimal options');
assert.equal(v1(RFC_OPTIONS), RFC_V1, 'full options');
});

test('exception thrown when > 10k ids created in 1ms', () => {
assert.throws(function () {
v1({ msecs: TIME, nsecs: 10000 });
}, 'throws when > 10K ids created in 1 ms');
});

test('clock regression by msec', () => {
// Verify clock regression bumps clockseq
const uidt = v1({ msecs: TIME });
const uidtb = v1({ msecs: TIME - 1 });
assert.ok(
parseInt(uidtb.split('-')[3], 16) - parseInt(uidt.split('-')[3], 16) === 1,
'Clock regression by msec increments the clockseq'
);
});

test('clock regression by nsec', () => {
// Verify clock regression bumps clockseq
const uidtn = v1({ msecs: TIME, nsecs: 10 });
const uidtnb = v1({ msecs: TIME, nsecs: 9 });
assert.ok(
parseInt(uidtnb.split('-')[3], 16) - parseInt(uidtn.split('-')[3], 16) === 1,
'Clock regression by nsec increments the clockseq'
);
});

const fullOptions = {
msecs: 1321651533573,
nsecs: 5432,
clockseq: 0x385c,
node: Uint8Array.of(0x61, 0xcd, 0x3c, 0xbb, 0x32, 0x10),
};

test('explicit options produce expected id', () => {
// Verify explicit options produce expected id
const id = v1(fullOptions);
assert.ok(
id === 'd9428888-122b-11e1-b85c-61cd3cbb3210',
'Explicit options produce expected id'
);
test('v1(options) equality', () => {
assert.notEqual(v1({ msecs: TIME }), v1({ msecs: TIME }), 'UUIDs with minimal options differ');
assert.equal(v1(RFC_OPTIONS), v1(RFC_OPTIONS), 'UUIDs with full options are identical');
});

test('explicit options.random produces expected id', () => {
function rng() {
return Uint8Array.of(
0x10,
0x91,
0x56,
0xbe,
0xc4,
0xfb,
0xc1,
0xea,
0x71,
0xb4,
0xef,
0xe1,
0x67,
0x1c,
0x58,
0x36
);
}

const id = v1({
msecs: 1321651533573,
nsecs: 5432,
rng,
});
assert.strictEqual(id, 'd9428888-122b-11e1-81ea-119156bec4fb');
});

test('ids spanning 1ms boundary are 100ns apart', () => {
// Verify adjacent ids across a msec boundary are 1 time unit apart
const u0 = v1({ msecs: TIME, nsecs: 9999 });
const u1 = v1({ msecs: TIME + 1, nsecs: 0 });

const before = u0.split('-')[0];
const after = u1.split('-')[0];
const dt = parseInt(after, 16) - parseInt(before, 16);
assert.ok(dt === 1, 'Ids spanning 1ms boundary are 100ns apart');
});

const expectedBytes = Uint8Array.of(
217,
66,
136,
136,
18,
43,
17,
225,
184,
92,
97,
205,
60,
187,
50,
16
);

test('fills one UUID into a buffer as expected', () => {
const buffer = new Uint8Array(16);
const result = v1(fullOptions, buffer);
assert.deepEqual(buffer, expectedBytes);
const result = v1(RFC_OPTIONS, buffer);
assert.deepEqual(buffer, RFC_V1_BYTES);
assert.strictEqual(buffer, result);
});

test('fills two UUIDs into a buffer as expected', () => {
const buffer = new Uint8Array(32);
v1(fullOptions, buffer, 0);
v1(fullOptions, buffer, 16);
v1(RFC_OPTIONS, buffer, 0);
v1(RFC_OPTIONS, buffer, 16);

const expectedBuf = new Uint8Array(32);
expectedBuf.set(expectedBytes);
expectedBuf.set(expectedBytes, 16);
expectedBuf.set(RFC_V1_BYTES);
expectedBuf.set(RFC_V1_BYTES, 16);

assert.deepEqual(buffer, expectedBuf);
});
});

test('v1() state transitions', () => {
// Test fixture for internal state passed into updateV1State function
const PRE_STATE = {
msecs: 10,
nsecs: 20,
clockseq: 0x1234,
node: Uint8Array.of(0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc),
};

// Note: The test code, below, passes RFC_RANDOM as the `rnds` argument for
// convenience. This allows us to test that fields have been initialized from
// the rnds argument by testing for RFC_OPTIONS values in the output state.

const tests = [
{
title: 'initial state',
state: {},
now: 10,
expected: {
msecs: 10, // -> now
nsecs: 0, // -> init
clockseq: RFC_OPTIONS.clockseq, // -> random
node: RFC_OPTIONS.node, // -> random
},
},
{
title: 'same time interval',
state: { ...PRE_STATE },
now: PRE_STATE.msecs,
expected: {
...PRE_STATE,
nsecs: 21, // -> +1
},
},
{
title: 'new time interval',
state: { ...PRE_STATE },
now: PRE_STATE.msecs + 1,
expected: {
...PRE_STATE,
msecs: PRE_STATE.msecs + 1, // -> +1
nsecs: 0, // -> init
},
},
{
title: 'same time interval (nsecs overflow)',
state: { ...PRE_STATE, nsecs: 9999 },
now: PRE_STATE.msecs,
expected: {
...PRE_STATE,
nsecs: 0, // -> init
clockseq: RFC_OPTIONS.clockseq, // -> init
node: RFC_OPTIONS.node, // -> init
},
},
{
title: 'time regression',
state: { ...PRE_STATE },
now: PRE_STATE.msecs - 1,
expected: {
...PRE_STATE,
msecs: PRE_STATE.msecs - 1, // -> now
clockseq: RFC_OPTIONS.clockseq, // -> init
node: RFC_OPTIONS.node, // -> init
},
},
];
for (const { title, state, now, expected } of tests) {
assert.deepStrictEqual(updateV1State(state, now, RFC_RANDOM), expected, `Failed: ${title}`);
}
});
36 changes: 18 additions & 18 deletions src/test/v6.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,29 @@ describe('v6', () => {
const V6_ID = '1ef21d2f-1207-6660-8c4f-419efbd44d48';

const fullOptions = {
msecs: 1321651533573,
nsecs: 5432,
msecs: 0x133b891f705,
nsecs: 0x1538,
clockseq: 0x385c,
node: Uint8Array.of(0x61, 0xcd, 0x3c, 0xbb, 0x32, 0x10),
};

const EXPECTED_BYTES = Uint8Array.of(
30,
17,
34,
189,
148,
40,
104,
136,
184,
92,
97,
205,
60,
187,
50,
16
0x1e,
0x11,
0x22,
0xbd,
0x94,
0x28,
0x68,
0x88,
0xb8,
0x5c,
0x61,
0xcd,
0x3c,
0xbb,
0x32,
0x10
);

test('default behavior', () => {
Expand Down
Loading

0 comments on commit a33ff8d

Please sign in to comment.