Refacto du code coté Python, java et coté angular afin de mieux séparer les responsabilité et d'avoir moins de répétitivité dans le code.
Some checks failed
E2E Tests / e2e (push) Waiting to run
Tests unitaires / Web (Angular · vitest + couverture) (push) Successful in 26s
Build & Push Images / tests (push) Failing after 13s
Build & Push Images / build (brain) (push) Has been skipped
Build & Push Images / build (core) (push) Has been skipped
Build & Push Images / build (web) (push) Has been skipped
Build & Push Images / build-switcher (push) Has been skipped
Tests unitaires / Brain (Python · pytest + couverture) (push) Successful in 1m12s
Tests unitaires / Core (Java · mvn test + JaCoCo) (push) Failing after 1m28s

Mise en place de tests unitaires coté Python et Angular
Mise en place de la couverture de test directement dans le workflow : le programme ne build pas si jamais un test échoue
Passage en v0.16.2 en conséquence
This commit is contained in:
2026-06-18 15:59:10 +02:00
parent eb78a75621
commit 4d049274f9
68 changed files with 4433 additions and 1360 deletions

View File

@@ -0,0 +1,149 @@
"""Tests du _TreeMerger de l'import de campagne (app.application.import_campaign).
Cœur du REDUCE : fusion par nom (insensible à la casse) des sous-arbres
arc→chapitre→scène→pièce produits morceau par morceau, + accumulation des PNJ.
"""
from __future__ import annotations
from app.application.import_campaign import _TreeMerger
def test_single_chunk_builds_full_tree():
m = _TreeMerger()
m.add([{
"name": "Acte I", "description": "intro",
"chapters": [{
"name": "Ch1", "description": "d",
"scenes": [{
"name": "Sc1", "description": "s",
"player_narration": "PN", "gm_notes": "GM",
"rooms": [{"name": "R1", "description": "rd", "enemies": "gob", "loot": "or"}],
}],
}],
}])
arcs = m.result()
assert len(arcs) == 1
arc = arcs[0]
assert arc.name == "Acte I"
assert arc.arc_type == "LINEAR"
sc = arc.chapters[0].scenes[0]
assert sc.player_narration == "PN"
assert sc.gm_notes == "GM"
room = sc.rooms[0]
assert (room.name, room.enemies, room.loot) == ("R1", "gob", "or")
def test_case_insensitive_arc_and_chapter_merge():
m = _TreeMerger()
m.add([{"name": "Acte I", "chapters": [{"name": "Ch1", "scenes": []}]}])
m.add([{"name": "acte i", "chapters": [{"name": "ch1", "scenes": []},
{"name": "Ch2", "scenes": []}]}])
arcs = m.result()
assert len(arcs) == 1
assert {c.name for c in arcs[0].chapters} == {"Ch1", "Ch2"}
def test_description_first_non_empty_wins():
m = _TreeMerger()
m.add([{"name": "A", "description": "", "chapters": []}])
m.add([{"name": "A", "description": "vraie", "chapters": []}])
m.add([{"name": "A", "description": "autre", "chapters": []}])
assert m.result()[0].description == "vraie"
def test_hub_type_wins_if_any_chunk_signals_it():
m = _TreeMerger()
m.add([{"name": "A", "type": "LINEAR", "chapters": []}])
m.add([{"name": "A", "type": "HUB", "chapters": []}])
assert m.result()[0].arc_type == "HUB"
def _scene(narr=None, gm=None):
s = {"name": "S"}
if narr is not None:
s["player_narration"] = narr
if gm is not None:
s["gm_notes"] = gm
return {"name": "A", "chapters": [{"name": "C", "scenes": [s]}]}
def test_scene_narration_concatenated_across_chunks():
m = _TreeMerger()
m.add([_scene(narr="début")])
m.add([_scene(narr="suite")])
sc = m.result()[0].chapters[0].scenes[0]
assert sc.player_narration == "début\n\nsuite"
def test_scene_field_dedups_exact_overlap():
m = _TreeMerger()
m.add([_scene(gm="texte identique")])
m.add([_scene(gm="texte identique")])
assert m.result()[0].chapters[0].scenes[0].gm_notes == "texte identique"
def test_scene_field_takes_superset_version():
m = _TreeMerger()
m.add([_scene(gm="court")])
m.add([_scene(gm="court et bien plus long")])
assert m.result()[0].chapters[0].scenes[0].gm_notes == "court et bien plus long"
def test_npcs_longest_description_wins():
m = _TreeMerger()
m.add_npcs([{"name": "Thorin", "description": "court"}])
m.add_npcs([{"name": "thorin", "description": "une description bien plus complète"}])
npcs = m.npcs()
assert len(npcs) == 1
assert npcs[0].name == "Thorin"
assert npcs[0].description == "une description bien plus complète"
def test_counts():
m = _TreeMerger()
m.add([{"name": "A", "chapters": [
{"name": "C1", "scenes": [{"name": "S1"}, {"name": "S2"}]},
{"name": "C2", "scenes": []},
]}])
assert m.counts() == (1, 2, 2)
def test_blank_names_are_skipped():
m = _TreeMerger()
m.add([{"name": "", "chapters": []},
{"name": " ", "chapters": []},
{"name": "OK", "chapters": [{"name": "", "scenes": []}]}])
arcs = m.result()
assert len(arcs) == 1
assert arcs[0].name == "OK"
assert arcs[0].chapters == []
def test_merge_chapters_consolidation():
m = _TreeMerger()
m.add([{"name": "A", "chapters": [
{"name": "Intro", "scenes": [{"name": "S1"}]},
{"name": "Introduction", "scenes": [{"name": "S2"}]},
]}])
assert m.merge_chapters("Intro", ["Introduction"]) is True
chapters = m.result()[0].chapters
assert len(chapters) == 1
assert {s.name for s in chapters[0].scenes} == {"S1", "S2"}
def test_merge_chapters_unknown_target_returns_false():
m = _TreeMerger()
m.add([{"name": "A", "chapters": [{"name": "Intro", "scenes": []}]}])
assert m.merge_chapters("Inexistant", ["Intro"]) is False
def test_merge_scenes_consolidation():
m = _TreeMerger()
m.add([{"name": "A", "chapters": [{"name": "C", "scenes": [
{"name": "Combat", "gm_notes": "x"},
{"name": "Le combat", "gm_notes": "y"},
]}]}])
assert m.merge_scenes("C", "Combat", ["Le combat"]) is True
scenes = m.result()[0].chapters[0].scenes
assert len(scenes) == 1
assert scenes[0].name == "Combat"