Files
ai-typewriter/tests/test_credentials.py
T
Hermes Agent 3e58380f80 Rework AI-Typewriter en application d'arrière-plan multi-profils
- Rollback gestion d'équations : écriture littérale caractère par caractère
  (suppression de math_format.py et de la normalisation Unicode).
- Nouveau profil par défaut 'Mathématiques (LaTeX)' : le modèle émet du
  LaTeX encadré [EQ]...[/EQ], intercepté par KeyStepper qui déclenche
  Alt+= en début d'équation et -> en fin.
- Config multi-profils dans %APPDATA%/ai-typewriter/config.json
  (ConfigStore + profils par défaut auto-créés).
- Icône de zone de notification (pystray) : Ouvrir les logs (temps réel),
  Ajouter/Modifier un profil, Gérer l'authentification, Quitter.
- Sélecteur de modèles : modèles locaux Ollama + recherche/téléchargement
  depuis la bibliothèque publique ; fournisseurs tiers (OpenAI, OpenRouter,
  Gemini, custom).
- Clés d'API stockées de façon sécurisée dans le Gestionnaire d'identifiants
  Windows via keyring (jamais en clair dans config.json).
- Tests : 44 tests verts (stepper, profils, clients IA, credentials,
  catalogue de modèles, moteur).
2026-09-18 09:48:46 +02:00

97 lines
2.7 KiB
Python

"""Tests du stockage sécurisé (keyring mocké pour éviter tout accès au
gestionnaire d'identifiants réel de la machine)."""
import pytest
from ai_typewriter.credentials import CredentialError, SecureStore, get_cred
class FakeKeyring:
"""Mini stub de keyring en mémoire, exposant la même interface."""
_data = {}
@classmethod
def reset(cls):
cls._data = {}
@classmethod
def set_password(cls, service, username, password):
cls._data[(service, username)] = password
@classmethod
def get_password(cls, service, username):
return cls._data.get((service, username))
@classmethod
def delete_password(cls, service, username):
cls._data.pop((service, username), None)
class FakeKeyringErrors:
class PasswordDeleteError(RuntimeError):
pass
def test_store_and_get(monkeypatch):
import ai_typewriter.credentials as cred
monkeypatch.setattr(cred, "keyring", FakeKeyring)
monkeypatch.setattr(cred.keyring, "errors", FakeKeyringErrors, raising=False)
FakeKeyring.reset()
store = SecureStore("test-service")
store.store("openai", "sk-secret")
assert store.get("openai") == "sk-secret"
assert store.has("openai") is True
def test_empty_credential_returns_empty(monkeypatch):
import ai_typewriter.credentials as cred
monkeypatch.setattr(cred, "keyring", FakeKeyring)
FakeKeyring.reset()
store = SecureStore()
assert store.get("") == ""
assert store.has("") is False
def test_get_cred_helper_creates_store(monkeypatch):
import ai_typewriter.credentials as cred
monkeypatch.setattr(cred, "keyring", FakeKeyring)
monkeypatch.setattr(cred.keyring, "errors", FakeKeyringErrors, raising=False)
FakeKeyring.reset()
# via un store passé explicitement
store = SecureStore("t")
store.store("k", "v")
assert get_cred(store, "k") == "v"
# via None (crée un store par défaut, mais retombe sur service réel) —
# on vérifie que quelques accesseurs ne plantent pas.
assert get_cred(None, "") == ""
def test_store_invalid_credential_name(monkeypatch):
import ai_typewriter.credentials as cred
monkeypatch.setattr(cred, "keyring", FakeKeyring)
FakeKeyring.reset()
store = SecureStore()
with pytest.raises(CredentialError):
store.store("", "secret")
def test_delete(monkeypatch):
import ai_typewriter.credentials as cred
monkeypatch.setattr(cred, "keyring", FakeKeyring)
monkeypatch.setattr(cred.keyring, "errors", FakeKeyringErrors, raising=False)
FakeKeyring.reset()
store = SecureStore("t")
store.store("gemini", "cle")
store.delete("gemini")
assert store.has("gemini") is False