Ajoute source_page/source_excerpt à tous les types (biologie, imagerie, traitements, actes CCAM, antécédents, complications). Convertit antecedents et complications en types structurés (Antecedent/Complication) avec validators backward-compat pour les vieux JSON. Étend _apply_source_tracking à tous les éléments du dossier. Ajoute un endpoint /api/source-text/ et un modal interactif dans le viewer avec surlignage du texte source. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
158 lines
4.7 KiB
Python
158 lines
4.7 KiB
Python
"""Tests pour le viewer Flask."""
|
|
|
|
import pytest
|
|
|
|
from src.viewer.app import create_app, compute_group_stats, severity_badge, format_duration, format_cpam_text
|
|
from src.config import DossierMedical, Diagnostic, ActeCCAM
|
|
|
|
|
|
@pytest.fixture
|
|
def app():
|
|
app = create_app()
|
|
app.config["TESTING"] = True
|
|
return app
|
|
|
|
|
|
@pytest.fixture
|
|
def client(app):
|
|
return app.test_client()
|
|
|
|
|
|
class TestGroupStats:
|
|
def test_group_stats(self):
|
|
items = [
|
|
{
|
|
"dossier": DossierMedical(
|
|
diagnostics_associes=[
|
|
Diagnostic(texte="HTA", cim10_suggestion="I10"),
|
|
Diagnostic(texte="Diabète", cim10_suggestion="E11.9", est_cma=True),
|
|
],
|
|
actes_ccam=[
|
|
ActeCCAM(texte="Cholé", code_ccam_suggestion="HMFC004"),
|
|
],
|
|
alertes_codage=["Alerte 1", "Alerte 2"],
|
|
),
|
|
},
|
|
{
|
|
"dossier": DossierMedical(
|
|
diagnostics_associes=[
|
|
Diagnostic(texte="Obésité", cim10_suggestion="E66.0"),
|
|
],
|
|
actes_ccam=[
|
|
ActeCCAM(texte="TDM", code_ccam_suggestion="ZCQK002"),
|
|
],
|
|
alertes_codage=[],
|
|
),
|
|
},
|
|
]
|
|
stats = compute_group_stats(items)
|
|
assert stats["das_count"] == 3
|
|
assert stats["actes_count"] == 2
|
|
assert stats["alertes_count"] == 2
|
|
assert stats["cma_count"] == 1
|
|
|
|
def test_group_stats_empty(self):
|
|
stats = compute_group_stats([])
|
|
assert stats["das_count"] == 0
|
|
assert stats["alertes_count"] == 0
|
|
|
|
|
|
class TestSeverityBadgeFilter:
|
|
def test_severe(self):
|
|
result = severity_badge("severe")
|
|
assert "Sévère" in result
|
|
assert "#dc2626" in result
|
|
|
|
def test_modere(self):
|
|
result = severity_badge("modere")
|
|
assert "Modéré" in result
|
|
|
|
def test_leger(self):
|
|
result = severity_badge("leger")
|
|
assert "Léger" in result
|
|
|
|
def test_none(self):
|
|
result = severity_badge(None)
|
|
assert result == ""
|
|
|
|
def test_unknown(self):
|
|
result = severity_badge("inconnu")
|
|
assert result == ""
|
|
|
|
|
|
class TestFormatDuration:
|
|
def test_none(self):
|
|
assert format_duration(None) == ""
|
|
|
|
def test_seconds_only(self):
|
|
assert format_duration(45.3) == "45.3s"
|
|
|
|
def test_minutes(self):
|
|
assert format_duration(150.0) == "2min 30s"
|
|
|
|
def test_exact_minutes(self):
|
|
assert format_duration(120.0) == "2min"
|
|
|
|
def test_large_duration(self):
|
|
assert format_duration(1257.65) == "20min 57s"
|
|
|
|
|
|
class TestIndexPageLoads:
|
|
def test_index_page_loads(self, client):
|
|
response = client.get("/")
|
|
assert response.status_code == 200
|
|
assert b"Dossiers" in response.data
|
|
|
|
|
|
class TestFormatCpamText:
|
|
def test_plain_text(self):
|
|
result = format_cpam_text("Un simple paragraphe.")
|
|
assert "<p" in result
|
|
assert "Un simple paragraphe." in result
|
|
|
|
def test_bullet_list(self):
|
|
result = format_cpam_text("- Premier argument\n- Deuxième argument")
|
|
assert "<ul" in result
|
|
assert "<li>Premier argument</li>" in result
|
|
assert "<li>Deuxième argument</li>" in result
|
|
|
|
def test_mixed_text_and_bullets(self):
|
|
text = "Introduction\n- Point A\n- Point B\nConclusion"
|
|
result = format_cpam_text(text)
|
|
assert "<p" in result
|
|
assert "<ul" in result
|
|
assert "<li>Point A</li>" in result
|
|
assert "Conclusion" in result
|
|
|
|
def test_none_input(self):
|
|
result = format_cpam_text(None)
|
|
assert result == ""
|
|
|
|
def test_empty_input(self):
|
|
result = format_cpam_text("")
|
|
assert result == ""
|
|
|
|
def test_html_escaping(self):
|
|
result = format_cpam_text("Test <script>alert('xss')</script>")
|
|
assert "<script>" not in result
|
|
assert "<script>" in result
|
|
|
|
|
|
class TestDetailPageLoads:
|
|
def test_detail_page_404(self, client):
|
|
"""Un fichier inexistant retourne 404."""
|
|
response = client.get("/dossier/nonexistent.json")
|
|
assert response.status_code == 404
|
|
|
|
|
|
class TestSourceTextEndpoint:
|
|
def test_source_text_404_nonexistent(self, client):
|
|
"""Un dossier inexistant retourne 404."""
|
|
response = client.get("/api/source-text/nonexistent_dossier")
|
|
assert response.status_code == 404
|
|
|
|
def test_source_text_security_path_traversal(self, client):
|
|
"""Path traversal bloqué."""
|
|
response = client.get("/api/source-text/../../etc")
|
|
assert response.status_code in (403, 404)
|