import pytest from types import SimpleNamespace from voice_transcriptor.services.credentials import CredentialError, CredentialService class FakeKeyring: def __init__(self) -> None: self.credential = None self.calls: list[tuple[str, str, str | None]] = [] def get_credential(self, service: str, account: str | None): self.calls.append(("get", service, account)) return self.credential def set_password(self, service: str, account: str, value: str) -> None: self.calls.append(("set", service, account, value)) self.credential = SimpleNamespace(username=account, password=value) def test_get_api_key_uses_existing_windows_credential_target() -> None: backend = FakeKeyring() backend.credential = SimpleNamespace(username="stored-user", password="secret") assert CredentialService(backend).get_api_key() == "secret" assert backend.calls == [("get", "OPENAI_API_KEY", None)] def test_has_api_key_reflects_keyring_value() -> None: backend = FakeKeyring() service = CredentialService(backend) assert service.has_api_key() is False backend.credential = SimpleNamespace(username="stored-user", password="secret") assert service.has_api_key() is True def test_set_api_key_rejects_blank_values() -> None: backend = FakeKeyring() with pytest.raises(CredentialError, match="API key"): CredentialService(backend).set_api_key(" \t") assert backend.calls == [] def test_set_api_key_persists_nonblank_value() -> None: backend = FakeKeyring() backend.credential = SimpleNamespace(username="stored-user", password="old") CredentialService(backend).set_api_key("secret") assert backend.calls == [ ("get", "OPENAI_API_KEY", None), ("set", "OPENAI_API_KEY", "stored-user", "secret"), ] def test_set_api_key_creates_target_with_stable_username_when_missing() -> None: backend = FakeKeyring() CredentialService(backend).set_api_key("secret") assert backend.calls == [ ("get", "OPENAI_API_KEY", None), ("set", "OPENAI_API_KEY", "OPENAI_API_KEY", "secret"), ] @pytest.mark.parametrize("method", ["get_credential", "set_password"]) def test_backend_failures_are_sanitized(method: str) -> None: class FailingKeyring: def get_credential(self, service: str, account: str | None): raise RuntimeError("backend leaked secret") def set_password(self, service: str, account: str, value: str) -> None: raise RuntimeError("backend leaked secret") service = CredentialService(FailingKeyring()) with pytest.raises(CredentialError) as caught: if method == "get_credential": service.get_api_key() else: service.set_api_key("secret") assert "secret" not in str(caught.value).lower() assert caught.value.__cause__ is None