Steph680/ormuz
0
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 