- base.yaml: suppression commentaires verbose, normalisation quotes YAML - Templates: corrections mineures cpam.html, detail.html, dim.html, index.html - admin_rules.html: ajustements interface admin règles - test_referentiels.py: mise à jour imports Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
182 lines
6.3 KiB
Python
182 lines
6.3 KiB
Python
"""Tests pour le gestionnaire de référentiels et les routes Flask associées."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
import pytest
|
|
|
|
from src.viewer.referentiels import ReferentielManager
|
|
from src.config import ALLOWED_EXTENSIONS, UPLOAD_MAX_SIZE_MB
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Tests ReferentielManager
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestReferentielManager:
|
|
|
|
@pytest.fixture
|
|
def manager(self, tmp_path):
|
|
return ReferentielManager(tmp_path / "refs")
|
|
|
|
def test_add_file(self, manager):
|
|
ref = manager.add_file("guide.pdf", b"fake pdf content")
|
|
assert ref["filename"] == "guide.pdf"
|
|
assert ref["extension"] == ".pdf"
|
|
assert ref["status"] == "uploaded"
|
|
assert ref["size_bytes"] == len(b"fake pdf content")
|
|
assert ref["chunks_count"] == 0
|
|
|
|
def test_list_all(self, manager):
|
|
manager.add_file("a.txt", b"hello")
|
|
manager.add_file("b.csv", b"col1,col2")
|
|
assert len(manager.list_all()) == 2
|
|
|
|
def test_get(self, manager):
|
|
ref = manager.add_file("guide.pdf", b"content")
|
|
found = manager.get(ref["id"])
|
|
assert found is not None
|
|
assert found["filename"] == "guide.pdf"
|
|
|
|
def test_get_not_found(self, manager):
|
|
assert manager.get("nonexistent") is None
|
|
|
|
def test_remove(self, manager):
|
|
ref = manager.add_file("guide.pdf", b"content")
|
|
assert manager.remove(ref["id"]) is True
|
|
assert len(manager.list_all()) == 0
|
|
assert manager.get(ref["id"]) is None
|
|
|
|
def test_remove_not_found(self, manager):
|
|
assert manager.remove("nonexistent") is False
|
|
|
|
def test_add_file_invalid_extension(self, manager):
|
|
with pytest.raises(ValueError, match="Extension"):
|
|
manager.add_file("malware.exe", b"evil")
|
|
|
|
def test_add_file_too_large(self, manager):
|
|
big_data = b"x" * (UPLOAD_MAX_SIZE_MB * 1024 * 1024 + 1)
|
|
with pytest.raises(ValueError, match="volumineux"):
|
|
manager.add_file("big.pdf", big_data)
|
|
|
|
def test_persistence(self, tmp_path):
|
|
"""L'index persiste entre les instances."""
|
|
dir_path = tmp_path / "refs"
|
|
m1 = ReferentielManager(dir_path)
|
|
m1.add_file("a.txt", b"hello")
|
|
|
|
m2 = ReferentielManager(dir_path)
|
|
assert len(m2.list_all()) == 1
|
|
assert m2.list_all()[0]["filename"] == "a.txt"
|
|
|
|
def test_file_stored_on_disk(self, manager, tmp_path):
|
|
ref = manager.add_file("test.txt", b"file content here")
|
|
stored_path = manager._base / ref["stored_name"]
|
|
assert stored_path.exists()
|
|
assert stored_path.read_bytes() == b"file content here"
|
|
|
|
def test_remove_deletes_file(self, manager):
|
|
ref = manager.add_file("test.txt", b"content")
|
|
stored_path = manager._base / ref["stored_name"]
|
|
assert stored_path.exists()
|
|
manager.remove(ref["id"])
|
|
assert not stored_path.exists()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Tests chunking générique
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestChunking:
|
|
|
|
def test_chunk_txt(self, tmp_path):
|
|
from src.medical.rag_index import chunk_user_file
|
|
|
|
txt_file = tmp_path / "test.txt"
|
|
txt_file.write_text(
|
|
"Premier paragraphe avec assez de mots pour le seuil.\n\n"
|
|
"Deuxième paragraphe avec encore plus de mots pour dépasser le minimum.\n\n"
|
|
"Court\n\n"
|
|
"Troisième paragraphe qui devrait aussi être un chunk valide.",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
chunks = chunk_user_file(txt_file, "test_doc")
|
|
assert len(chunks) >= 2 # au moins 2 paragraphes assez longs
|
|
assert all(c.document == "test_doc" for c in chunks)
|
|
|
|
def test_chunk_csv(self, tmp_path):
|
|
from src.medical.rag_index import chunk_user_file
|
|
|
|
csv_file = tmp_path / "test.csv"
|
|
csv_file.write_text(
|
|
"code,description,note\n"
|
|
"K85.1,Pancréatite aiguë biliaire,diagnostic fréquent\n"
|
|
"I10,Hypertension essentielle,comorbidité courante\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
chunks = chunk_user_file(csv_file, "csv_doc")
|
|
assert len(chunks) == 2
|
|
assert "K85.1" in chunks[0].text
|
|
assert "I10" in chunks[1].text
|
|
|
|
def test_chunk_unsupported_extension(self, tmp_path):
|
|
from src.medical.rag_index import chunk_user_file
|
|
|
|
bad_file = tmp_path / "test.xyz"
|
|
bad_file.write_text("content")
|
|
|
|
chunks = chunk_user_file(bad_file, "bad")
|
|
assert chunks == []
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Tests routes Flask
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestReferentielRoutes:
|
|
|
|
@pytest.fixture
|
|
def app(self, tmp_path):
|
|
"""Crée une app Flask de test avec un manager temporaire."""
|
|
from src.viewer.app import create_app
|
|
with patch.dict(os.environ, {"T2A_DEMO_USER": "", "T2A_DEMO_PASS": ""}):
|
|
app = create_app()
|
|
app.config["TESTING"] = True
|
|
yield app
|
|
|
|
@pytest.fixture
|
|
def client(self, app):
|
|
return app.test_client()
|
|
|
|
def test_admin_page_loads(self, client):
|
|
resp = client.get("/admin/referentiels")
|
|
assert resp.status_code == 200
|
|
assert "Referentiels" in resp.data.decode()
|
|
|
|
def test_upload_no_file(self, client):
|
|
resp = client.post("/admin/referentiels/upload")
|
|
assert resp.status_code == 400
|
|
data = resp.get_json()
|
|
assert "error" in data
|
|
|
|
def test_upload_valid_file(self, client):
|
|
from io import BytesIO
|
|
data = {
|
|
"file": (BytesIO(b"test content"), "doc.txt"),
|
|
}
|
|
resp = client.post("/admin/referentiels/upload", data=data, content_type="multipart/form-data")
|
|
result = resp.get_json()
|
|
assert resp.status_code == 200
|
|
assert result["ok"] is True
|
|
assert result["referentiel"]["filename"] == "doc.txt"
|
|
|
|
def test_delete_nonexistent(self, client):
|
|
resp = client.delete("/admin/referentiels/nonexistent")
|
|
assert resp.status_code == 404
|