// Form session keys (Phase 12.2, D-02): 32 random bytes from // crypto.getRandomValues as unpadded base64url, 43 characters, never the // same twice, and only ever sent in the two headers. import { describe, expect, it, vi } from 'vitest' import { CHILD_SESSION_HEADER, SESSION_HEADER, newSessionKey } from '../../src/app/sessionKey' const SERVER_PATTERN = /^[A-Za-z0-9_-]{32,128}$/ describe('newSessionKey', () => { it('is 43 base64url characters the server accepts', () => { for (let i = 0; i < 50; i++) { const key = newSessionKey() expect(key).toHaveLength(43) expect(key).toMatch(/^[A-Za-z0-9_-]+$/) expect(key).toMatch(SERVER_PATTERN) expect(key).not.toContain('=') } }) it('draws 32 bytes from crypto.getRandomValues and encodes them', () => { const spy = vi.spyOn(globalThis.crypto, 'getRandomValues').mockImplementation((array: T): T => { const bytes = array as unknown as Uint8Array bytes.fill(0xfb) bytes[31] = 0xff return array }) const key = newSessionKey() expect(spy).toHaveBeenCalledTimes(1) const arg = spy.mock.calls[0]![0] as unknown as Uint8Array expect(arg).toBeInstanceOf(Uint8Array) expect(arg.byteLength).toBe(32) // 0xfb bytes give "+" and "/" in standard base64: base64url turns them // into "-" and "_". expect(key).toBe('-_v7-_v7-_v7-_v7-_v7-_v7-_v7-_v7-_v7-_v7-_8') }) it('differs on every call', () => { const keys = new Set(Array.from({ length: 200 }, () => newSessionKey())) expect(keys.size).toBe(200) }) it('names the two headers', () => { expect(SESSION_HEADER).toBe('X-Session-Key') expect(CHILD_SESSION_HEADER).toBe('X-Child-Session-Key') }) })