=================================================================== QA #47 — Infrastructure Redmine : recherche par clé (champ custom), création + anti-doublons/régression Epic parent : #24 | Branche : feat/47-redmine-security-adapter PR #27 -> dev | Backend uniquement (api/) SHA testé : bf0d87d (origin/feat/47-redmine-security-adapter) Date : 2026-06-11 | Agent : QA manuel (#25) NOTE : aucun secret/clé API n'est inclus dans cette preuve. =================================================================== ENVIRONNEMENT python : api/.venv (FastAPI épinglé) cmd : PYTHONPATH=src .venv/bin/python -m pytest -q tests/test_redmine_scan_adapter.py ------------------------------------------------------------------- SMOKE TEST 1/3 — Tests de contrat respx (9 tests, hors réseau) ------------------------------------------------------------------- ......... [100%] 9 passed in 0.46s ------------------------------------------------------------------- SMOKE TEST 1/3 — détail (verbose, noms de scénarios) ------------------------------------------------------------------- tests/test_redmine_scan_adapter.py::test_adapter_is_a_scan_ticket_port PASSED [ 11%] tests/test_redmine_scan_adapter.py::test_find_by_key_open_ticket_via_custom_field PASSED [ 22%] tests/test_redmine_scan_adapter.py::test_find_by_key_no_match_returns_none PASSED [ 33%] tests/test_redmine_scan_adapter.py::test_find_by_key_only_closed_ticket_is_regression PASSED [ 44%] tests/test_redmine_scan_adapter.py::test_find_by_key_fallback_matches_on_description PASSED [ 55%] tests/test_redmine_scan_adapter.py::test_create_ticket_files_security_ticket PASSED [ 66%] tests/test_redmine_scan_adapter.py::test_create_ticket_without_field_embeds_key_in_body PASSED [ 77%] tests/test_redmine_scan_adapter.py::test_reopen_ticket_resets_status_and_notes PASSED [ 88%] tests/test_redmine_scan_adapter.py::test_note_still_open_adds_note_only PASSED [100%] ------------------------------------------------------------------- SMOKE TEST 2/3 — Câblage import / use-case ------------------------------------------------------------------- OK : RedmineScanTicketAdapter implémente ScanTicketPort (port défini en #46) OK : build_redmine_scan_ticket_adapter + SECURITY_TAG importables OK : RedmineField.SECURITY_KEY = 12 OK : SECURITY_TAG = [Sécurité] ------------------------------------------------------------------- SMOKE TEST 3/3 — Qualité statique (module concerné) ------------------------------------------------------------------- $ ruff check scan_adapter.py + test_redmine_scan_adapter.py All checks passed! $ mypy scan_adapter.py Success: no issues found in 1 source file =================================================================== MAPPING CRITÈRES D'ACCEPTATION -> RÉSULTAT =================================================================== AC1 Recherche par clé via champ custom 'security_key' (id 12), avec repli titre/description documenté ............... OK - test_find_by_key_open_ticket_via_custom_field - test_find_by_key_fallback_matches_on_description - docstring scan_adapter.py : repli documenté (lignes 21-26) AC2 Anti-doublon : aucun ticket si un ticket OUVERT porte déjà la clé (resolved=False) ......................... OK - test_find_by_key_open_ticket_via_custom_field - requête pass 1 scoping status_id=open vérifié AC3 Régression : nouveau ticket si seuls des tickets FERMÉS portent la clé (resolved=True) ................ OK - test_find_by_key_only_closed_ticket_is_regression AC4 Création conforme : tag [Sécurité], statut Submitted, clé persistée dans security_key ...................... OK - test_create_ticket_files_security_ticket - test_create_ticket_without_field_embeds_key_in_body AC5 Tests respx verts ; chemin RUN_REDMINE_LIVE présent ; ruff + mypy passent .................................. OK - 9/9 respx verts ; ruff+mypy clean ; gate dev : 423 passed / 2 skipped (live gated) x2 NOTE AC1 (provisioning) : le champ custom security_key (id 12) n'est pas encore provisionné sur le Redmine live ; l'adaptateur retombe alors sur le repli titre/description (clé embarquée dans le corps à la création). Comportement vérifié par test_find_by_key_fallback_matches_on_description et test_create_ticket_without_field_embeds_key_in_body. VERDICT : PASS — tous les critères OK. ===================================================================