Spaces:
Sleeping
fix(ci): tests HTR-United remote + bandit B324 SHA-1 cache
Browse filesÉchecs CI sur la branche claude/fix-module-rewiring-MHssX :
``CI / Tests`` (Linux+macOS+Windows, Python 3.11/3.12) + ``CI /
Security scanners``.
## 1. Tests HTR-United remote en CI
Phase 4.4 du chantier post-rewrite avait fait passer le router
``/api/htr-united/catalogue`` de ``from_demo()`` exclusif à
``from_remote(timeout=5)`` avec fallback démo. En local avec
``PICARONES_HTR_UNITED_OFFLINE=1``, ``from_demo()`` est forcé et les
tests passent. En CI sans cette variable, ``from_remote()`` réussit
mais le catalogue distant retourne parfois des entrées avec
``id=""`` (schéma YAML évolutif), ce qui cassait
``test_known_entry_calls_importer`` (422 Pydantic au lieu de 200).
Sprint J (commit 9228764) avait déjà appliqué le fix à
``test_sprint6_web_interface.py::test_import_valid_entry``, mais
``tests/web/routers/test_s4_importers_router.py::test_known_entry_calls_importer``
avait été oublié. Même correctif appliqué : filtre sur entrées
non-vides + skip si catalogue uniquement entrées vides.
## 2. Security scanner — bandit B324 (SHA-1 high severity)
Phase 2.3 du chantier (commit 5e48c0b) avait introduit
``hashlib.sha1`` dans ``_engine_config_for_fingerprint`` pour
identifier un prompt LLM dans le nom du fichier partial — pas une
garantie crypto, juste un cache key court. Bandit (qui ne peut pas
deviner l'intention) signale ça en High severity B324.
Fix : ajout du flag ``usedforsecurity=False`` (Python 3.9+) qui
exprime explicitement l'intention non-crypto et neutralise le faux
positif.
Vérifications locales :
- ``bandit -r picarones/ -ll`` : 0 High, 19 Low (vs 1 High avant).
- Suite sans ``PICARONES_HTR_UNITED_OFFLINE`` : 4686 passed,
14 skipped (vs 1 failed avant).
https://claude.ai/code/session_01ArfZ8kcgv7Cyda7VbJVmpn
|
@@ -528,8 +528,13 @@ def _engine_config_for_fingerprint(engine: Any) -> dict:
|
|
| 528 |
# Hasher le prompt pour éviter de polluer le nom du fichier
|
| 529 |
# partiel avec un prompt multi-lignes (et de fuiter le
|
| 530 |
# contenu d'un prompt institutionnel dans un nom de fichier).
|
|
|
|
|
|
|
|
|
|
|
|
|
| 531 |
cfg["prompt_sha1"] = hashlib.sha1(
|
| 532 |
str(prompt).encode("utf-8"),
|
|
|
|
| 533 |
).hexdigest()[:12]
|
| 534 |
llm = getattr(engine, "llm_adapter", None)
|
| 535 |
if llm is not None:
|
|
|
|
| 528 |
# Hasher le prompt pour éviter de polluer le nom du fichier
|
| 529 |
# partiel avec un prompt multi-lignes (et de fuiter le
|
| 530 |
# contenu d'un prompt institutionnel dans un nom de fichier).
|
| 531 |
+
# SHA-1 utilisé comme identifiant de cache uniquement
|
| 532 |
+
# (fingerprint partial store, pas une garantie crypto) —
|
| 533 |
+
# ``usedforsecurity=False`` neutralise le faux positif
|
| 534 |
+
# bandit B324 et exprime l'intention pour le reviewer.
|
| 535 |
cfg["prompt_sha1"] = hashlib.sha1(
|
| 536 |
str(prompt).encode("utf-8"),
|
| 537 |
+
usedforsecurity=False,
|
| 538 |
).hexdigest()[:12]
|
| 539 |
llm = getattr(engine, "llm_adapter", None)
|
| 540 |
if llm is not None:
|
|
@@ -109,9 +109,16 @@ class TestHTRUnitedImport:
|
|
| 109 |
assert "non trouvée" in r.json()["detail"]
|
| 110 |
|
| 111 |
def test_known_entry_calls_importer(self, tmp_path: Path) -> None:
|
| 112 |
-
"""Avec un entry_id du catalogue
|
| 113 |
``import_htr_united_corpus``. On mocke pour éviter le
|
| 114 |
-
download réel.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
from fastapi.testclient import TestClient
|
| 116 |
|
| 117 |
app = _make_app()
|
|
@@ -120,12 +127,18 @@ class TestHTRUnitedImport:
|
|
| 120 |
) as mock_import:
|
| 121 |
mock_import.return_value = {"imported": 3, "output_dir": str(tmp_path)}
|
| 122 |
|
| 123 |
-
# Récupère un entry_id du catalogue démo.
|
| 124 |
with TestClient(app) as client:
|
| 125 |
catalog = client.get("/api/htr-united/catalogue").json()
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
|
| 130 |
r = client.post(
|
| 131 |
"/api/htr-united/import",
|
|
@@ -135,7 +148,7 @@ class TestHTRUnitedImport:
|
|
| 135 |
"max_samples": 3,
|
| 136 |
},
|
| 137 |
)
|
| 138 |
-
assert r.status_code == 200
|
| 139 |
assert mock_import.called
|
| 140 |
|
| 141 |
|
|
|
|
| 109 |
assert "non trouvée" in r.json()["detail"]
|
| 110 |
|
| 111 |
def test_known_entry_calls_importer(self, tmp_path: Path) -> None:
|
| 112 |
+
"""Avec un entry_id du catalogue, l'endpoint appelle
|
| 113 |
``import_htr_united_corpus``. On mocke pour éviter le
|
| 114 |
+
download réel.
|
| 115 |
+
|
| 116 |
+
Phase 4.4 du chantier post-rewrite : le router utilise
|
| 117 |
+
désormais ``from_remote()`` avec fallback démo ; en mode
|
| 118 |
+
remote certaines entrées peuvent avoir un ``id`` vide
|
| 119 |
+
(schéma YAML distant évolutif). On filtre pour récupérer
|
| 120 |
+
un id réellement importable, sinon on skip — un id vide
|
| 121 |
+
serait rejeté par Pydantic en 422 (sécurité OK)."""
|
| 122 |
from fastapi.testclient import TestClient
|
| 123 |
|
| 124 |
app = _make_app()
|
|
|
|
| 127 |
) as mock_import:
|
| 128 |
mock_import.return_value = {"imported": 3, "output_dir": str(tmp_path)}
|
| 129 |
|
|
|
|
| 130 |
with TestClient(app) as client:
|
| 131 |
catalog = client.get("/api/htr-united/catalogue").json()
|
| 132 |
+
non_empty = [
|
| 133 |
+
e for e in catalog.get("entries", []) if e.get("id")
|
| 134 |
+
]
|
| 135 |
+
if not non_empty:
|
| 136 |
+
pytest.skip(
|
| 137 |
+
"Catalogue HTR-United sans entrée avec id non-vide "
|
| 138 |
+
"(probable en CI réseau-restreint, ``from_remote`` "
|
| 139 |
+
"fallback démo limite).",
|
| 140 |
+
)
|
| 141 |
+
entry_id = non_empty[0]["id"]
|
| 142 |
|
| 143 |
r = client.post(
|
| 144 |
"/api/htr-united/import",
|
|
|
|
| 148 |
"max_samples": 3,
|
| 149 |
},
|
| 150 |
)
|
| 151 |
+
assert r.status_code == 200, r.text
|
| 152 |
assert mock_import.called
|
| 153 |
|
| 154 |
|