"""Unit: session token minting and `SessionRecord` (A16). The token is the player's only identity: 24 random bytes, urlsafe base64 — the same entropy the browser side generates with `crypto.getRandomValues`. These tests lock the shape that `SessionStore` (task 04) builds on. """ from __future__ import annotations import string import pytest from undercooked.server.clock import FakeClock from undercooked.server.sessions import mint_token, new_session pytestmark = pytest.mark.unit _URLSAFE = set(string.ascii_letters + string.digits + "-_") def test_mint_token_is_24_random_bytes_of_urlsafe_base64() -> None: token = mint_token() assert len(token) == 32 # 24 bytes -> 32 base64url characters assert set(token) <= _URLSAFE assert "+" not in token and "/" not in token and "=" not in token def test_minted_tokens_are_distinct() -> None: tokens = {mint_token() for _ in range(100)} assert len(tokens) == 100 def test_new_session_mints_a_token_and_stamps_the_injected_clock() -> None: clock = FakeClock(start=42.0) session = new_session("Ada", clock) assert len(session.token) == 32 assert session.display_name == "Ada" assert session.created_at == 42.0 anonymous = new_session(None, clock) assert anonymous.display_name is None assert anonymous.token != session.token assert anonymous.created_at == 42.0