"""KeyStore — 다이제스트 인증, 스코프, 키 생성 테스트.""" from __future__ import annotations import json import pytest from luke_scribe.api.deps import KeyStore, Principal from luke_scribe.config import Settings from luke_scribe.errors import AuthError, ScopeDenied @pytest.fixture def settings(tmp_path) -> Settings: return Settings( _env_file=None, api_keys="key-one,key-admin:admin,transcribe", api_key_file=str(tmp_path / "api_keys.json"), ) class TestKeyStoreAuth: def test_plain_key_authenticates(self, settings: Settings): ks = KeyStore(settings) p = ks.authenticate("key-one") assert p.key_id.startswith("k-key-one") assert "transcribe" in p.scopes def test_scoped_key(self, settings: Settings): ks = KeyStore(settings) p = ks.authenticate("key-admin") assert {"admin", "transcribe"} <= p.scopes def test_wrong_key_rejected(self, settings: Settings): ks = KeyStore(settings) with pytest.raises(AuthError): ks.authenticate("key-wrong") def test_empty_key_rejected(self, settings: Settings): ks = KeyStore(settings) with pytest.raises(AuthError): ks.authenticate(None) def test_digest_only_on_disk(self, settings: Settings, tmp_path): """평문 키는 저장하지 않고 다이제스트만 파일에 기록 (Eng P14).""" created = KeyStore(settings).create_key( scopes=["transcribe"], save_path=settings.api_key_file ) raw = created["key"] data = json.loads(tmp_path.joinpath("api_keys.json").read_text()) assert raw not in json.dumps(data) entry = data["keys"][0] assert entry["digest"] != raw assert len(entry["digest"]) == 64 # sha256 hex def test_file_keys_loaded(self, settings: Settings, tmp_path): ks = KeyStore(settings) created = ks.create_key(scopes=["transcribe"], save_path=settings.api_key_file) # 새 인스턴스가 파일에서 로드 ks2 = KeyStore(settings) p = ks2.authenticate(created["key"]) assert "transcribe" in p.scopes class TestPrincipalScopes: def test_require_scope_ok(self): p = Principal(key_id="k-1", scopes={"transcribe"}) p.require_scope("transcribe") # no raise def test_require_scope_denied(self): p = Principal(key_id="k-1", scopes={"transcribe"}) with pytest.raises(ScopeDenied): p.require_scope("admin") def test_require_scope_empty(self): p = Principal(key_id="k-1", scopes=set()) with pytest.raises(ScopeDenied): p.require_scope("transcribe") class TestDigest: def test_same_key_same_digest(self, settings: Settings): ks = KeyStore(settings) assert ks._digest("abc") == ks._digest("abc") assert ks._digest("abc") != ks._digest("abd")