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

129
brain/tests/test_chat.py Normal file
View File

@@ -0,0 +1,129 @@
"""Tests de la construction du system prompt du chat (app.application.chat).
Assertions par INCLUSION (présence des données/sections clés) plutôt que sur le
texte exact des consignes : robuste aux retouches de formulation, tout en
vérifiant que chaque contexte est bien injecté.
"""
from __future__ import annotations
from app.application.chat import ChatUseCase
from app.domain.models import (
ArcSummary,
CampaignStructuralContext,
ChapterSummary,
CharacterSummary,
ChatMessage,
GameSystemContext,
JournalEntrySummary,
LoreStructuralContext,
NarrativeEntityContext,
NpcSummary,
PageContext,
PageSummary,
SceneSummary,
SessionContext,
)
def _build(**kw) -> str:
return ChatUseCase(None).build_system_prompt(**kw)
def _empty_lore() -> LoreStructuralContext:
return LoreStructuralContext(lore_name="L", lore_description=None, folders={}, tags=[])
def test_base_prompt_without_context_is_non_empty():
assert len(_build()) > 0
def test_lore_block_renders_pages_with_values_tags_and_links():
lore = LoreStructuralContext(
lore_name="Eldoria", lore_description="un monde sombre",
folders={"PNJ": [PageSummary(
title="Aragorn", template_name="Personnage",
values={"apparence": "grand et noble"}, tags=["héros"],
related_page_titles=["Gondor"])]},
tags=["dark-fantasy"])
p = _build(lore_context=lore)
assert "Eldoria" in p
assert "un monde sombre" in p
assert "Aragorn" in p
assert "apparence" in p and "grand et noble" in p
assert "héros" in p
assert "Gondor" in p
def test_empty_lore_signals_vide():
assert "Lore vide" in _build(lore_context=_empty_lore())
def test_page_context_block_lists_fields_and_empty_marker():
page = PageContext(title="Aragorn", template_name="Personnage",
template_fields=["apparence", "histoire"],
values={"apparence": "grand"})
p = _build(page_context=page)
assert "PAGE EN COURS" in p
assert "Aragorn" in p
assert "apparence" in p
assert "(vide)" in p # 'histoire' sans valeur
def test_campaign_block_with_arc_and_empty_pj_npc_and_no_lore_note():
camp = CampaignStructuralContext(
campaign_name="La Malédiction", campaign_description="horreur gothique",
arcs=[ArcSummary(name="Acte I", description="intro",
chapters=[ChapterSummary(name="Ch1", description="",
scenes=[SceneSummary(name="Sc1", description="")])])],
characters=[], npcs=[])
p = _build(campaign_context=camp)
assert "CAMPAGNE COURANTE" in p
assert "La Malédiction" in p
assert "Acte I" in p
assert "aucune fiche" in p # pas de PJ
assert "aucun univers" in p # pas de lore lié
def test_campaign_with_characters_npcs_and_lore_present_note():
camp = CampaignStructuralContext(
campaign_name="C", campaign_description=None, arcs=[],
characters=[CharacterSummary(name="Tav", snippet="roublarde")],
npcs=[NpcSummary(name="Strahd", snippet="vampire de Barovia")])
p = _build(campaign_context=camp, lore_context=_empty_lore())
assert "Tav" in p and "roublarde" in p
assert "Strahd" in p and "vampire de Barovia" in p
assert "liée à l'univers" in p
def test_game_system_narrative_and_session_sections_injected():
gs = GameSystemContext(system_name="Nimble", system_description=None,
sections={"Combat": "règles de combat"})
narr = NarrativeEntityContext(entity_type="scene", title="L'auberge du Portail",
fields={"ambiance": "tendue"})
sess = SessionContext(
session_name="Séance 3", active=True, started_at=None,
entries=[JournalEntrySummary(type="EVENT", content="Le pont s'effondre", occurred_at=None)],
previous_events=[])
p = _build(lore_context=_empty_lore(), game_system_context=gs,
narrative_entity=narr, session_context=sess)
assert "Nimble" in p
assert "L'auberge du Portail" in p
assert "Séance 3" in p
assert "Le pont s'effondre" in p
async def test_stream_passes_built_prompt_to_llm():
class FakeChatLLM:
def __init__(self) -> None:
self.system_prompt = None
async def stream_chat(self, messages, *, system_prompt=None, temperature=None):
self.system_prompt = system_prompt
yield "tok"
llm = FakeChatLLM()
out = [t async for t in ChatUseCase(llm).stream(
[ChatMessage(role="user", content="salut")],
lore_context=LoreStructuralContext("Eldoria", None, {}, []))]
assert out == ["tok"]
assert "Eldoria" in llm.system_prompt