CoolFace
Apppublic

Steph680/ormuz

sourceHugging Faceupdated 2d agoView on Hugging Face
0likes
test_export.py166 linesDownload Raw Back to tests
1# -*- coding: utf-8 -*-2"""Tests de l'extrait destiné à un modèle de langage."""3 4import asyncio5import json6from datetime import timedelta7 8from app import db, export9from app.state import utc_now10 11 12def _semer(tmp_path, monkeypatch, articles=(), extras=None):13    """articles = liste de (titre, jours_avant, source, grappe, relevance)."""14    monkeypatch.setenv("DB_PATH", str(tmp_path / "t.db"))15 16    async def run():17        await db.init_db()18        async with db.connect() as conn:19            for i, (titre, jours, source, grappe, rel) in enumerate(articles):20                quand = (utc_now() - timedelta(days=jours)).isoformat()21                await conn.execute(22                    "INSERT INTO news_events (url, published_at, title, source,"23                    " lang, category, relevance, cluster_id, corps_en)"24                    " VALUES (?, ?, ?, ?, 'en', 'incident_securite', ?, ?, ?)",25                    (f"https://ex.org/{i}", quand, titre, source, rel, grappe,26                     titre + " — corps complet du message."))27            if extras:28                for sql, params in extras:29                    await conn.execute(sql, params)30            await conn.commit()31        return await export.construire(90)32 33    return asyncio.run(run())34 35 36def test_extrait_vide_reste_exploitable(tmp_path, monkeypatch):37    extrait = _semer(tmp_path, monkeypatch)38    assert extrait["manifeste"]["periode"]["jours"] == 9039    assert extrait["evenements"] == []40    # Le manifeste décrit les sections même vides : un modèle sait alors que41    # la donnée est absente, et non qu'elle a été omise.42    assert set(extrait["manifeste"]["sections"]) >= {43        "evenements", "economie", "radar", "trafic_portwatch"}44 45 46def test_un_evenement_par_grappe_pas_par_article(tmp_path, monkeypatch):47    """Cinquante reprises d'un même fait gonfleraient le fichier sans rien48    apporter : l'extrait porte une ligne par fait, et dit combien d'articles49    l'ont repris."""50    arts = [(f"Tanker attacked ({i})", 3, f"Media{i}", 500, 80) for i in range(12)]51    arts.append(("Autre fait sans rapport", 3, "Reuters", 700, 80))52    extrait = _semer(tmp_path, monkeypatch, arts)53    assert len(extrait["evenements"]) == 254    grappe = next(e for e in extrait["evenements"] if "Tanker" in e["titre"])55    assert grappe["articles_dans_la_grappe"] == 1256 57 58def test_articles_non_pertinents_exclus(tmp_path, monkeypatch):59    """Neuf dixièmes du corpus brut sont du bruit de collecte : les exporter60    ferait analyser le bruit."""61    arts = [("Tanker attacked", 2, "Reuters", 1, 80),62            ("Resultat de football", 2, "Blog", 2, 10)]63    extrait = _semer(tmp_path, monkeypatch, arts)64    assert [e["titre"] for e in extrait["evenements"]] == ["Tanker attacked"]65 66 67def test_hors_periode_exclu(tmp_path, monkeypatch):68    arts = [("Recent", 2, "Reuters", 1, 80), ("Tres ancien", 200, "Reuters", 2, 80)]69    extrait = _semer(tmp_path, monkeypatch, arts)70    assert [e["titre"] for e in extrait["evenements"]] == ["Recent"]71 72 73def test_troncature_signalee(tmp_path, monkeypatch):74    """Un extrait plafonné doit le dire : sinon un modèle conclurait à une75    accalmie là où il ne manque que des lignes."""76    monkeypatch.setattr(export, "MAX_EVENEMENTS", 3)77    arts = [(f"Fait {i}", 2, "Reuters", i, 80) for i in range(10)]78    extrait = _semer(tmp_path, monkeypatch, arts)79    assert len(extrait["evenements"]) == 380    assert extrait["manifeste"]["evenements_tronques"] is True81    assert any("plafonn" in a for a in82               extrait["manifeste"]["avertissements_de_lecture"])83 84 85def test_texte_tronque_a_la_limite(tmp_path, monkeypatch):86    monkeypatch.setattr(export, "CORPS_MAX", 20)87    extrait = _semer(tmp_path, monkeypatch,88                     [("Un titre suffisamment long pour depasser", 2, "R", 1, 80)])89    assert len(extrait["evenements"][0]["texte"]) == 2090 91 92def test_aucune_donnee_personnelle_exportee(tmp_path, monkeypatch):93    """Un extrait destiné à être confié à un tiers ne doit contenir ni compte,94    ni empreinte de mot de passe, ni message d'utilisateur."""95    extras = [96        ("INSERT INTO contacts (recu_le, auteur, email, message, statut)"97         " VALUES (?, 'Societe X', 'client@exemple.fr', 'message prive', 'envoye')",98         (utc_now().isoformat(),)),99        ("INSERT INTO users (name, password_hash, role, active, created_at)"100         " VALUES ('client-secret', '$2b$12$empreintebidon', 'client', 1, ?)",101         (utc_now().isoformat(),)),102    ]103    extrait = _semer(tmp_path, monkeypatch,104                     [("Tanker attacked", 2, "Reuters", 1, 80)], extras)105    brut = json.dumps(extrait, ensure_ascii=False)106    for interdit in ("client@exemple.fr", "message prive",107                     "$2b$12$empreintebidon", "client-secret"):108        assert interdit not in brut109    # Et l'absence est documentée, pour qu'elle ne passe pas pour un oubli.110    assert "contacts" in extrait["manifeste"]["non_exporte"]111    assert "users" in extrait["manifeste"]["non_exporte"]112 113 114def test_avertissements_de_lecture_presents(tmp_path, monkeypatch):115    """C'est le manifeste qui rend l'extrait exploitable : sans lui, un modèle116    prendrait le proxy de presse pour une prime cotée et le retard de117    publication de PortWatch pour une chute de trafic."""118    extrait = _semer(tmp_path, monkeypatch)119    texte = " ".join(extrait["manifeste"]["avertissements_de_lecture"])120    assert "N'EST PAS UN PRIX" in texte121    assert "5 à 8 jours" in texte122    assert "INDÉPENDAMMENT de l'AIS" in texte123    assert "−3" in texte and "+3" in texte124 125 126def test_series_economiques_regroupees_par_serie(tmp_path, monkeypatch):127    base = utc_now().date()128    extras = []129    for i in range(3):130        jour = (base - timedelta(days=i)).isoformat()131        extras.append(("INSERT INTO eco_series (serie, jour, valeur)"132                       " VALUES ('brent', ?, ?)", (jour, 100.0 + i)))133        extras.append(("INSERT INTO eco_series (serie, jour, valeur)"134                       " VALUES ('wti', ?, ?)", (jour, 96.0 + i)))135    extrait = _semer(tmp_path, monkeypatch, extras=extras)136    assert set(extrait["economie"]) == {"brent", "wti"}137    assert len(extrait["economie"]["brent"]) == 3138    # Les séries sont ordonnées chronologiquement : une courbe se lit.139    jours = [p["jour"] for p in extrait["economie"]["brent"]]140    assert jours == sorted(jours)141 142 143def test_themes_de_presse_rendus_en_objet(tmp_path, monkeypatch):144    """Stockés en JSON encodé, ils seraient illisibles pour un modèle sans145    décodage : l'extrait les rend en objet."""146    jour = (utc_now().date() - timedelta(days=2)).isoformat()147    extras = [("INSERT INTO presse_jours (jour, articles, evenements, sources,"148               " themes, version, calcule_le) VALUES (?, 40, 12, 9, ?, 1, ?)",149               (jour, json.dumps({"cinetique": 20, "economique": 8}),150                utc_now().isoformat()))]151    extrait = _semer(tmp_path, monkeypatch, extras=extras)152    assert extrait["presse_quotidien"][0]["themes"]["cinetique"] == 20153 154 155def test_nom_de_fichier_date_et_periode():156    nom = export.nom_de_fichier(90)157    assert nom.startswith("sindbad-extrait-") and nom.endswith("-90j.json")158 159 160def test_extrait_serialisable_en_json(tmp_path, monkeypatch):161    """Rien d'exotique ne doit traîner : l'extrait part tel quel dans une162    réponse HTTP."""163    extrait = _semer(tmp_path, monkeypatch,164                     [("Tanker attacked", 2, "Reuters", 1, 80)])165    assert json.loads(json.dumps(extrait, ensure_ascii=False))["evenements"]166