Files
college-infos/tests/unit/test_redaction.py
Antoine Van Elstraete 19cbf8f13f fix(M9): corrections d'audit FIXME_M9 — secrets, messages, validation, tests
Cinq corrections de l'audit FIXME_M9 :
- redact_secrets() étendue avec extra_secrets pour masquer les clés brutes ;
  providers stockent SecretStr jusqu'à l'appel SDK.
- _build_prompt() inclut le contenu des messages (tronqué à 500 car.) ;
  prompt système renforcé contre l'injection.
- _validate_output() supprime les emojis et rejette titre/liste/HTML → None.
- Tests litellm utilisent importorskip + LITELLM_LOCAL_MODEL_COST_MAP=true.
- .env.example désactive l'IA par défaut (AI_ENABLED=false).
- Documentation §9.2-§9.5 alignée avec l'implémentation (SDK openai, SecretStr,
  factory réelle, validation sortie, politique hors réseau).

Co-authored-by: opencode/coder <coder@agents.invalid>
Co-authored-by: opencode/test-engineer <test-engineer@agents.invalid>
Co-authored-by: opencode/tech-writer <tech-writer@agents.invalid>
2026-09-07 19:01:25 +02:00

157 lines
4.9 KiB
Python

"""Tests unitaires pour les utilitaires de masquage des secrets.
Ce module vérifie que les fonctions de masquage des URLs, des en-têtes
et des secrets dans les textes fonctionnent correctement, sans fuite
d'informations sensibles.
"""
from __future__ import annotations
from pydantic import SecretStr
from pronote_sync.utils.redaction import redact_exception, redact_secrets, redact_url
def test_redact_url_with_sensitive_query_param() -> None:
"""Vérifie que les paramètres sensibles dans une URL sont masqués.
:return: None
"""
url = "https://example.fr/ical?icalsecurise=TOKEN123" # pragma: allowlist secret
redacted = redact_url(url)
assert "TOKEN123" not in redacted
assert "REDACTED" in redacted
def test_redact_url_with_embedded_auth() -> None:
"""Vérifie que les informations d'authentification intégrées dans une URL sont masquées.
:return: None
"""
url = "https://user:pass@host/path" # pragma: allowlist secret
redacted = redact_url(url)
assert "user" not in redacted
assert "pass" not in redacted
assert "REDACTED" in redacted
def test_redact_url_with_uppercase_scheme() -> None:
"""Vérifie que les URLs avec un schéma en majuscules sont traitées correctement.
:return: None
"""
url = "HTTPS://user:pass@host/path" # pragma: allowlist secret
redacted = redact_url(url)
assert "user" not in redacted
assert "pass" not in redacted
assert "REDACTED" in redacted
def test_redact_secrets_authorization_bearer() -> None:
"""Vérifie que les en-têtes Authorization: Bearer sont masqués.
:return: None
"""
text = "Authorization: Bearer mytoken123" # pragma: allowlist secret
redacted = redact_secrets(text)
assert "mytoken123" not in redacted
assert "REDACTED" in redacted
def test_redact_secrets_proxy_authorization_basic() -> None:
"""Vérifie que les en-têtes Proxy-Authorization: Basic sont masqués.
:return: None
"""
text = "Proxy-Authorization: Basic dXNlcjpwYXNz" # pragma: allowlist secret
redacted = redact_secrets(text)
assert "dXNlcjpwYXNz" not in redacted
assert "REDACTED" in redacted
def test_redact_secrets_lowercase_authorization() -> None:
"""Vérifie que les en-têtes authorization en minuscules sont masqués.
:return: None
"""
text = "authorization: Bearer tok" # pragma: allowlist secret
redacted = redact_secrets(text)
assert "tok" not in redacted
assert "REDACTED" in redacted
def test_redact_secrets_text_key_value() -> None:
"""Vérifie que les affectations clé=valeur dans du texte sont masquées.
:return: None
"""
text = "password=mysecret" # pragma: allowlist secret
redacted = redact_secrets(text)
assert "mysecret" not in redacted
assert "REDACTED" in redacted
def test_redact_secrets_json_key_value() -> None:
"""Vérifie que les secrets dans du JSON sont masqués.
:return: None
"""
text = '{"api_key": "myapikey"}' # pragma: allowlist secret
redacted = redact_secrets(text)
assert "myapikey" not in redacted
assert "REDACTED" in redacted
def test_redact_exception() -> None:
"""Vérifie que les exceptions contenant des secrets sont masquées.
:return: None
"""
exc = Exception("Authorization: Bearer leaked_token") # pragma: allowlist secret
redacted_msg = redact_exception(exc)
assert "leaked_token" not in redacted_msg
assert "REDACTED" in redacted_msg
def test_redact_url_preserves_host_and_path() -> None:
"""Vérifie que l'hôte et le chemin d'une URL sont préservés après masquage.
:return: None
"""
url = "https://user:pass@example.fr/path?icalsecurise=tok" # pragma: allowlist secret
redacted = redact_url(url)
assert "example.fr" in redacted
assert "/path" in redacted
assert "user" not in redacted
assert "pass" not in redacted
assert "tok" not in redacted
# --- Tests pour redact_secrets avec extra_secrets (FIXME_M9 Point 1) ---
def test_redact_secrets_with_extra_secrets_raw() -> None:
"""Vérifie que redact_secrets masque les secrets supplémentaires fournis sous forme brute."""
text = "text with sk-abc123"
redacted = redact_secrets(text, extra_secrets=["sk-abc123"])
assert "sk-abc123" not in redacted
assert "REDACTED" in redacted
def test_redact_secrets_with_extra_secrets_secret_str() -> None:
"""Vérifie que redact_secrets masque les secrets supplémentaires fournis sous SecretStr."""
text = "text with sk-abc123"
redacted = redact_secrets(text, extra_secrets=[SecretStr("sk-abc123")])
assert "sk-abc123" not in redacted
assert "REDACTED" in redacted
def test_redact_secrets_with_extra_secrets_empty_values() -> None:
"""Vérifie que redact_secrets ignore les valeurs vides dans extra_secrets."""
text = "text"
redacted = redact_secrets(text, extra_secrets=[""])
assert redacted == "text"
# Ensure trailing newline