import { describe, it, expect, afterEach, vi } from 'vitest'; import { generateId } from './id'; 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}$/i; describe('generateId', () => { afterEach(() => { vi.unstubAllGlobals(); }); it('U-01: returns a UUID v4-shaped string when crypto.randomUUID is available', () => { const id = generateId(); expect(id).toMatch(UUID_V4); }); it('U-02: falls back when crypto.randomUUID is undefined (insecure-context case)', () => { // Simulate an HTTP LAN origin: window.crypto exists but randomUUID is not exposed. const baseCrypto = globalThis.crypto; vi.stubGlobal('crypto', { getRandomValues: baseCrypto.getRandomValues.bind(baseCrypto) // no randomUUID }); const id = generateId(); expect(id).toMatch(UUID_V4); }); it('U-03: every call returns a unique value (uniqueness under fallback)', () => { const baseCrypto = globalThis.crypto; vi.stubGlobal('crypto', { getRandomValues: baseCrypto.getRandomValues.bind(baseCrypto) }); const ids = new Set(Array.from({ length: 50 }, generateId)); expect(ids.size).toBe(50); }); it('U-04: prefers crypto.randomUUID when present (does not drop into fallback)', () => { const spy = vi.fn(() => '11111111-1111-4111-8111-111111111111'); vi.stubGlobal('crypto', { randomUUID: spy, getRandomValues: globalThis.crypto.getRandomValues.bind(globalThis.crypto) }); const id = generateId(); expect(spy).toHaveBeenCalledTimes(1); expect(id).toBe('11111111-1111-4111-8111-111111111111'); }); });