Batch+realtime transcription API: faster-whisper engine w/ EngineOwner (single GPU owner, OOM downgrade chain, persisted attempted_profiles), hardware-adaptive device manager (T0-T3 VRAM tiers), Redis/in-proc job queue w/ leases + crash recovery, postprocess (glossary/rules/LLM w/ egress guard), privacy-first result store (UUID keys, source deleted after transcribe), retention sweeper, API-key auth (HMAC digests, scopes, job ownership), WebSocket realtime lane (LocalAgreement), CLI (detect/transcribe/bench/serve/key), Docker, benchmark runner. 127 mock-based tests pass; ruff clean. Includes verification checklist and autoplan review notes.
87 lines
2.9 KiB
Python
87 lines
2.9 KiB
Python
"""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")
|