"""Unit tests for ``neuronetz_gateway.auth.keys`` (key gen + prefix parsing). SPEC §11/§4.3 key scheme: a full key is ``nz_``; the **stored prefix** is ``full_key[:PREFIX_LEN]`` (the ``nz_`` namespace plus leading random chars) and is used as the Redis cache key / DB lookup. The whole full key is argon2id-hashed; the full key is shown once. Part of the 100%-coverage gate on ``auth/``. """ from __future__ import annotations import pytest from neuronetz_gateway.auth import keys from tests._skip import call_or_skip def test_namespace_and_prefix_len_match_spec() -> None: assert keys.KEY_NAMESPACE == "nz_" assert keys.PREFIX_LEN == 12 def test_generate_key_shape() -> None: gen = call_or_skip(keys.generate_key) assert gen.full_key.startswith(keys.KEY_NAMESPACE) # Full key = namespace + SECRET_LEN random chars. assert len(gen.full_key) == len(keys.KEY_NAMESPACE) + keys.SECRET_LEN # Stored prefix is exactly the first PREFIX_LEN chars of the full key and # therefore a literal prefix of it (SPEC §4.3 "first 12 chars"). assert gen.prefix == gen.full_key[: keys.PREFIX_LEN] assert len(gen.prefix) == keys.PREFIX_LEN assert gen.full_key.startswith(gen.prefix) def test_generate_key_is_unique() -> None: a = call_or_skip(keys.generate_key) b = call_or_skip(keys.generate_key) assert a.full_key != b.full_key assert a.prefix != b.prefix # CSPRNG => prefixes differ with overwhelming prob. def test_generated_key_is_url_safe_ascii() -> None: gen = call_or_skip(keys.generate_key) body = gen.full_key[len(keys.KEY_NAMESPACE) :] assert body.isascii() assert body.isalnum() # base62 alphabet, no separators assert " " not in gen.full_key def test_extract_prefix_roundtrips_generated_key() -> None: gen = call_or_skip(keys.generate_key) assert call_or_skip(keys.extract_prefix, gen.full_key) == gen.prefix def test_extract_prefix_rejects_bad_format() -> None: # Missing namespace / too short must raise rather than silently truncate. with pytest.raises((ValueError, NotImplementedError)): keys.extract_prefix("definitely-not-a-key") def test_extract_prefix_rejects_too_short() -> None: with pytest.raises((ValueError, NotImplementedError)): keys.extract_prefix("nz_short")