Files
iptvnator/libs/shared/interfaces/src/lib/random-id.util.spec.ts
T
4gray 1ab82b04a1 refactor(deps): drop uuid for a shared crypto-based id helper (#1266)
Supersedes #1252 and #872. uuid 14 is ESM-only, apps/web/jest.config.ts only
kept v9 working by mapping `^uuid$` at a `wrapper.mjs` that v14 no longer
ships, and the specifier also has to be synced in
libs/shared/m3u-utils/package.json or @nx/dependency-checks fails lint.

All four call sites only used `v4()`, so the dependency goes away instead.
`createRandomId()` prefers `crypto.randomUUID()` and falls back to building the
same v4 shape from `crypto.getRandomValues()` — that fallback is load-bearing,
because randomUUID is only exposed in secure contexts and the self-hosted PWA
is regularly served over plain http on a LAN address. getRandomValues stays
available there, and it is what uuid's own v4 used.

`@types/uuid` goes too; it only existed for the untyped v9 package.
2026-07-26 22:27:31 +02:00

85 lines
2.4 KiB
TypeScript

import { createRandomId } from './random-id.util';
const UUID_V4 =
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
describe('createRandomId', () => {
const nativeRandomUUID = crypto.randomUUID;
afterEach(() => {
Object.defineProperty(crypto, 'randomUUID', {
configurable: true,
value: nativeRandomUUID,
});
});
it('returns a v4 uuid', () => {
expect(createRandomId()).toMatch(UUID_V4);
});
it('returns unique ids', () => {
const ids = new Set(
Array.from({ length: 500 }, () => createRandomId())
);
expect(ids.size).toBe(500);
});
it('uses the native randomUUID when the context exposes it', () => {
const randomUUID = jest
.fn()
.mockReturnValue('11111111-1111-4111-8111-111111111111');
Object.defineProperty(crypto, 'randomUUID', {
configurable: true,
value: randomUUID,
});
expect(createRandomId()).toBe('11111111-1111-4111-8111-111111111111');
expect(randomUUID).toHaveBeenCalledTimes(1);
});
describe('without randomUUID (insecure context)', () => {
beforeEach(() => {
Object.defineProperty(crypto, 'randomUUID', {
configurable: true,
value: undefined,
});
});
it('still returns a v4 uuid from getRandomValues', () => {
expect(createRandomId()).toMatch(UUID_V4);
});
it('returns unique ids', () => {
const ids = new Set(
Array.from({ length: 500 }, () => createRandomId())
);
expect(ids.size).toBe(500);
});
it('sets the version and variant bits on all-zero randomness', () => {
jest.spyOn(crypto, 'getRandomValues').mockImplementation(
(buffer) => buffer
);
expect(createRandomId()).toBe(
'00000000-0000-4000-8000-000000000000'
);
});
it('keeps the remaining bits from getRandomValues', () => {
jest.spyOn(crypto, 'getRandomValues').mockImplementation(
(buffer) => {
(buffer as Uint8Array).fill(0xff);
return buffer;
}
);
expect(createRandomId()).toBe(
'ffffffff-ffff-4fff-bfff-ffffffffffff'
);
});
});
});