Spaces:
Running
Running
File size: 8,943 Bytes
94e0210 9011070 94e0210 d109222 9011070 94e0210 17cc547 9011070 17cc547 94e0210 17cc547 94e0210 17cc547 94e0210 9011070 17cc547 94e0210 17cc547 94e0210 9011070 17cc547 94e0210 17cc547 94e0210 9011070 17cc547 94e0210 17cc547 94e0210 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 | """Tests Sprint 95 โ B.4 : visualisation DAG d'un pipeline composรฉ.
Couvre :
1. ``build_pipeline_dag_html`` :
- vide / None โ ``""``
- 1 nลud โ SVG sans arรชte
- 2 nลuds + 1 arรชte
- 3 nลuds chaรฎnรฉs
- arรชtes auto-dรฉduites si non fournies
- couleur selon seuil de la mรฉtrique
- mode higher_is_better
2. Anti-injection sur nom de nลud, type d'artefact, nom de
mรฉtrique.
3. Affichage de la valeur de mรฉtrique formatรฉe.
4. Complรฉtude i18n FR/EN.
"""
from __future__ import annotations
import json
from pathlib import Path
from picarones.reports.html.renderers.pipeline_dag import build_pipeline_dag_html
def _load_labels(lang: str) -> dict:
p = (
Path(__file__).parent.parent.parent
/ "picarones" / "reports" / "i18n" / f"{lang}.json"
)
return json.loads(p.read_text(encoding="utf-8"))
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# 1. build_pipeline_dag_html
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class TestRender:
def test_empty_returns_empty(self) -> None:
assert build_pipeline_dag_html(None) == ""
assert build_pipeline_dag_html([]) == ""
def test_single_node_renders_svg_no_edge(self) -> None:
nodes = [{"name": "tess", "output_types": ["TEXT"]}]
html = build_pipeline_dag_html(nodes, _load_labels("fr"))
assert "<svg" in html
assert "tess" in html
# Pas de flรจche tracรฉe (pas d'arรชte)
assert "marker-end" not in html
def test_two_nodes_one_edge(self) -> None:
nodes = [
{"name": "ocr", "output_types": ["TEXT"]},
{"name": "llm", "input_types": ["TEXT"]},
]
edges = [{"from": "ocr", "to": "llm",
"artifact_type": "TEXT",
"metric_name": "cer",
"metric_value": 0.04}]
html = build_pipeline_dag_html(
nodes, _load_labels("fr"), edges=edges,
)
# Nลuds prรฉsents
assert "ocr" in html
assert "llm" in html
# รtiquettes d'arรชte
assert "TEXT" in html
assert "cer" in html
assert "4.0%" in html
# Flรจche prรฉsente
assert "marker-end" in html
def test_three_nodes_chain(self) -> None:
nodes = [
{"name": "a"}, {"name": "b"}, {"name": "c"},
]
edges = [
{"from": "a", "to": "b", "metric_value": 0.05},
{"from": "b", "to": "c", "metric_value": 0.10},
]
html = build_pipeline_dag_html(nodes, edges=edges)
# Deux flรจches
assert html.count("marker-end") == 2
def test_auto_edges_when_missing(self) -> None:
# Pas d'arรชtes fournies โ auto-dรฉduit sรฉquentielles
nodes = [{"name": "a"}, {"name": "b"}, {"name": "c"}]
html = build_pipeline_dag_html(nodes)
assert html.count("marker-end") == 2
def test_colour_green_for_low_cer(self) -> None:
# Sprint A7 (m-5) : palette Okabe-Ito (daltonien-friendly).
# Le test valide la sรฉmantique ยซ โค 0.05 โ bon ยป sans coder en
# dur le hex (qui peut รฉvoluer avec la palette). Comparaison
# via ``COLOR_GREEN`` du module canonique.
from picarones.reports._helpers.colors import COLOR_GREEN
nodes = [{"name": "a"}, {"name": "b"}]
edges = [{"from": "a", "to": "b",
"metric_value": 0.02}] # โค 0.05 โ bon
html = build_pipeline_dag_html(nodes, edges=edges)
assert COLOR_GREEN in html
def test_colour_yellow(self) -> None:
from picarones.reports._helpers.colors import COLOR_YELLOW
nodes = [{"name": "a"}, {"name": "b"}]
edges = [{"from": "a", "to": "b", "metric_value": 0.10}]
html = build_pipeline_dag_html(nodes, edges=edges)
assert COLOR_YELLOW in html
def test_colour_red_for_high_cer(self) -> None:
from picarones.reports._helpers.colors import COLOR_RED
nodes = [{"name": "a"}, {"name": "b"}]
edges = [{"from": "a", "to": "b", "metric_value": 0.30}]
html = build_pipeline_dag_html(nodes, edges=edges)
assert COLOR_RED in html
def test_higher_is_better_inverts(self) -> None:
# F1 = 0.95 = bonne qualitรฉ (haut)
from picarones.reports._helpers.colors import COLOR_GREEN
nodes = [{"name": "a"}, {"name": "b"}]
edges = [{"from": "a", "to": "b", "metric_value": 0.96}]
html = build_pipeline_dag_html(
nodes, edges=edges, higher_is_better=True,
)
assert COLOR_GREEN in html
def test_unknown_node_in_edge_skipped(self) -> None:
nodes = [{"name": "a"}, {"name": "b"}]
edges = [
{"from": "a", "to": "b", "metric_value": 0.05},
{"from": "ghost", "to": "b", "metric_value": 0.01},
]
html = build_pipeline_dag_html(nodes, edges=edges)
# Une seule flรจche valide
assert html.count("marker-end") == 1
def test_handles_missing_metric_value(self) -> None:
nodes = [{"name": "a"}, {"name": "b"}]
edges = [{"from": "a", "to": "b",
"artifact_type": "TEXT",
"metric_name": "cer"}] # pas de valeur
html = build_pipeline_dag_html(nodes, edges=edges)
assert "โ" in html or "cer" in html
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# 2. Anti-injection
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class TestAntiInjection:
def test_node_name(self) -> None:
nodes = [{"name": "<script>alert(1)</script>"}]
html = build_pipeline_dag_html(nodes, _load_labels("fr"))
assert "<script>alert" not in html
assert "<script>" in html
def test_artifact_type(self) -> None:
nodes = [{"name": "a"}, {"name": "b"}]
edges = [{"from": "a", "to": "b",
"artifact_type": "<img/>",
"metric_value": 0.05}]
html = build_pipeline_dag_html(nodes, edges=edges)
assert "<img/>" not in html
assert "<img" in html
def test_metric_name(self) -> None:
nodes = [{"name": "a"}, {"name": "b"}]
edges = [{"from": "a", "to": "b",
"metric_name": "<script>x",
"metric_value": 0.05}]
html = build_pipeline_dag_html(nodes, edges=edges)
assert "<script>x" not in html
assert "<script>" in html
def test_input_output_types(self) -> None:
nodes = [{"name": "a", "input_types": ["<svg/>"],
"output_types": ["<x>"]}]
html = build_pipeline_dag_html(nodes, _load_labels("fr"))
assert "<svg/>" not in html
assert "<svg" in html
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# 3. Rendu en anglais
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class TestI18nRendering:
def test_english(self) -> None:
nodes = [{"name": "a"}]
html = build_pipeline_dag_html(nodes, _load_labels("en"))
assert "Inspection tool" in html or "source of truth" in html
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# 4. Complรฉtude i18n
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
_KEYS = {
"dag_title", "dag_note", "dag_legend",
"dag_legend_green", "dag_legend_yellow", "dag_legend_red",
}
class TestI18nCompleteness:
def test_fr(self) -> None:
d = _load_labels("fr")
assert not _KEYS - d.keys()
def test_en(self) -> None:
d = _load_labels("en")
assert not _KEYS - d.keys()
|