Co-authored-by: Codex/gpt-5.6-terra <codex-gpt-5.6-terra@agents.invalid>
147 lines
5.3 KiB
Python
147 lines
5.3 KiB
Python
"""Tests unitaires du contrôle de secrets de déploiement."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
from types import ModuleType
|
|
from typing import TYPE_CHECKING
|
|
|
|
import pytest
|
|
|
|
if TYPE_CHECKING:
|
|
from _pytest.capture import CaptureFixture
|
|
|
|
|
|
@pytest.fixture
|
|
def secret_checker() -> ModuleType:
|
|
"""Charge le script de vérification sans l'exécuter comme programme.
|
|
|
|
:return: Module du script de contrôle de secrets.
|
|
:rtype: ModuleType
|
|
"""
|
|
script_path = Path(__file__).parents[2] / "scripts" / "check_secrets.py"
|
|
specification = importlib.util.spec_from_file_location("check_secrets", script_path)
|
|
assert specification is not None
|
|
assert specification.loader is not None
|
|
module = importlib.util.module_from_spec(specification)
|
|
sys.modules[specification.name] = module
|
|
try:
|
|
specification.loader.exec_module(module)
|
|
finally:
|
|
del sys.modules[specification.name]
|
|
return module
|
|
|
|
|
|
def test_main_accepts_clean_files_and_ignores_environment_file(
|
|
secret_checker: ModuleType, tmp_path: Path, capsys: CaptureFixture[str]
|
|
) -> None:
|
|
"""Vérifie qu'un dépôt propre réussit sans analyser le fichier d'environnement.
|
|
|
|
:param secret_checker: Module du script sous test.
|
|
:param tmp_path: Répertoire temporaire représentant un dépôt.
|
|
:param capsys: Fixture de capture de sortie.
|
|
:return: None
|
|
"""
|
|
(tmp_path / "application.py").write_text("value = 'safe'\n", encoding="utf-8")
|
|
ignored_environment_secret = 'password = "private-value"\n' # pragma: allowlist secret
|
|
(tmp_path / ".env").write_text(
|
|
ignored_environment_secret, encoding="utf-8"
|
|
) # secret-check: allow
|
|
|
|
assert secret_checker.main([], root=tmp_path) == 0
|
|
assert "OK:" in capsys.readouterr().out
|
|
|
|
|
|
def test_main_reports_a_literal_secret_without_disclosing_its_value(
|
|
secret_checker: ModuleType, tmp_path: Path, capsys: CaptureFixture[str]
|
|
) -> None:
|
|
"""Vérifie qu'un secret littéral échoue sans fuite de sa valeur.
|
|
|
|
:param secret_checker: Module du script sous test.
|
|
:param tmp_path: Répertoire temporaire représentant un dépôt.
|
|
:param capsys: Fixture de capture de sortie.
|
|
:return: None
|
|
"""
|
|
sentinel = "m14-literal-sentinel"
|
|
(tmp_path / "settings.py").write_text(
|
|
f'password = "{sentinel}"\n', encoding="utf-8"
|
|
) # secret-check: allow
|
|
|
|
assert secret_checker.main([], root=tmp_path) == 1
|
|
output = capsys.readouterr().out
|
|
assert "settings.py:1 (affectation-litterale)" in output
|
|
assert sentinel not in output
|
|
|
|
|
|
def test_main_reports_an_unquoted_configuration_secret_without_disclosing_its_value(
|
|
secret_checker: ModuleType, tmp_path: Path, capsys: CaptureFixture[str]
|
|
) -> None:
|
|
"""Vérifie qu'un secret de configuration non cité échoue sans fuite de sa valeur.
|
|
|
|
:param secret_checker: Module du script sous test.
|
|
:param tmp_path: Répertoire temporaire représentant un dépôt.
|
|
:param capsys: Fixture de capture de sortie.
|
|
:return: None
|
|
"""
|
|
sentinel = "m14-unquoted-sentinel"
|
|
(tmp_path / "settings.yaml").write_text(
|
|
f"password: {sentinel}\n", encoding="utf-8"
|
|
) # secret-check: allow
|
|
|
|
assert secret_checker.main([], root=tmp_path) == 1
|
|
output = capsys.readouterr().out
|
|
assert "settings.yaml:1 (affectation-litterale)" in output
|
|
assert sentinel not in output
|
|
|
|
|
|
def test_main_detects_sensitive_url_parameter(
|
|
secret_checker: ModuleType, tmp_path: Path, capsys: CaptureFixture[str]
|
|
) -> None:
|
|
"""Vérifie qu'un paramètre URL sensible déclenche un échec.
|
|
|
|
:param secret_checker: Module du script sous test.
|
|
:param tmp_path: Répertoire temporaire représentant un dépôt.
|
|
:param capsys: Fixture de capture de sortie.
|
|
:return: None
|
|
"""
|
|
sentinel = "m14-url-sentinel"
|
|
(tmp_path / "settings.yaml").write_text(
|
|
f"url: https://example.invalid/calendar?icalsecurise={sentinel}\n", encoding="utf-8"
|
|
) # secret-check: allow
|
|
|
|
assert secret_checker.main([], root=tmp_path) == 1
|
|
output = capsys.readouterr().out
|
|
assert "settings.yaml:1 (parametre-url)" in output
|
|
assert sentinel not in output
|
|
|
|
|
|
def test_staged_mode_inspects_only_paths_provided_by_git(
|
|
secret_checker: ModuleType, tmp_path: Path, capsys: CaptureFixture[str]
|
|
) -> None:
|
|
"""Vérifie que l'option staged ignore les fichiers non indexés.
|
|
|
|
:param secret_checker: Module du script sous test.
|
|
:param tmp_path: Répertoire temporaire représentant un dépôt.
|
|
:param capsys: Fixture de capture de sortie.
|
|
:return: None
|
|
"""
|
|
(tmp_path / "indexed.py").write_text("answer = 42\n", encoding="utf-8")
|
|
untracked_secret = 'api_key = "m14-untracked-sentinel"\n' # pragma: allowlist secret
|
|
(tmp_path / "untracked.py").write_text(
|
|
untracked_secret, encoding="utf-8"
|
|
) # secret-check: allow
|
|
|
|
def runner(*_args: object, **_kwargs: object) -> subprocess.CompletedProcess[str]:
|
|
"""Simule Git avec un seul fichier indexé.
|
|
|
|
:return: Résultat Git simulé.
|
|
:rtype: subprocess.CompletedProcess[str]
|
|
"""
|
|
return subprocess.CompletedProcess([], 0, stdout="indexed.py\0", stderr="")
|
|
|
|
assert secret_checker.main(["--staged"], root=tmp_path, runner=runner) == 0
|
|
assert "OK:" in capsys.readouterr().out
|