import assert from "node:assert/strict"; import { webcrypto } from "node:crypto"; import { describe, it } from "node:test"; import { z } from "zod"; import { createClientId } from "../src/frontend/utils/client-id.js"; const uuidV4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/; describe("client id", () => { it("uses crypto.randomUUID when the context provides it", () => { const id = createClientId({ getRandomValues: () => { throw new Error("fallback must not be used"); }, randomUUID: () => "11111111-2222-4333-8444-555555555555", }); assert.equal(id, "11111111-2222-4333-8444-555555555555"); }); it("falls back to getRandomValues outside a secure context", () => { const insecure = { getRandomValues: webcrypto.getRandomValues.bind(webcrypto), } as Pick; const ids = new Set(); for (let index = 0; index < 200; index += 1) { const id = createClientId(insecure); assert.match(id, uuidV4); assert.equal(z.string().uuid().safeParse(id).success, true); ids.add(id); } assert.equal(ids.size, 200); }); it("sets version and variant bits regardless of the random bytes", () => { const allOnes = { getRandomValues: (array: T) => { if (array instanceof Uint8Array) { array.fill(0xff); } return array; }, } as Pick; assert.equal(createClientId(allOnes), "ffffffff-ffff-4fff-bfff-ffffffffffff"); }); });