Files
rpa_vision_v3/tests/unit/test_lea_message_contract.py

281 lines
8.4 KiB
Python

"""Tests du contrat de messages humains pour Lea."""
from __future__ import annotations
import pytest
from agent_v0.agent_v1.ui.message_contract import (
MAX_FIELD_CHARS,
MessageContractError,
coerce_supervised_pause_message,
format_supervised_pause_from_mapping,
format_supervised_pause_message,
validate_supervised_pause_message,
validate_visible_message,
warn_visible_message,
)
def _valid_pause(**overrides: str) -> str:
fields = {
"intention": "ouvrir le dossier patient dans Aiva Urgence",
"attendu": "voir la fiche du patient ouverte avec la liste des passages",
"vu": "la page d'accueil Aiva Urgence sans le dossier patient",
"demande": "ouvrir le dossier patient puis me rendre la main",
}
fields.update(overrides)
return format_supervised_pause_message(**fields)
def _raw_pause(**overrides: str) -> str:
fields = {
"intention": "ouvrir le dossier patient dans Aiva Urgence",
"attendu": "voir la fiche du patient ouverte avec la liste des passages",
"vu": "la page d'accueil Aiva Urgence sans le dossier patient",
"demande": "ouvrir le dossier patient puis me rendre la main",
}
fields.update(overrides)
return "\n".join(
[
f"J'essaie de : {fields['intention']}",
f"J'attendais : {fields['attendu']}",
f"Je vois : {fields['vu']}",
f"Peux-tu : {fields['demande']}",
]
)
def _issue_codes(message: str) -> set[str]:
return {issue.code for issue in validate_supervised_pause_message(message).issues}
def test_format_supervised_pause_has_exact_four_field_structure():
message = _valid_pause()
assert message.splitlines() == [
"J'essaie de : ouvrir le dossier patient dans Aiva Urgence",
"J'attendais : voir la fiche du patient ouverte avec la liste des passages",
"Je vois : la page d'accueil Aiva Urgence sans le dossier patient",
"Peux-tu : ouvrir le dossier patient puis me rendre la main",
]
assert validate_supervised_pause_message(message).valid
def test_format_from_mapping_accepts_runtime_aliases():
message = format_supervised_pause_from_mapping(
{
"trying_to": "selectionner le passage aux urgences",
"expected": "voir le formulaire de codage du passage",
"observed": "la liste des passages reste affichee",
"request": "selectionner le bon passage puis me rendre la main",
}
)
assert "J'essaie de : selectionner le passage aux urgences" in message
assert validate_supervised_pause_message(message).valid
@pytest.mark.parametrize(
"bad_phrase",
[
"un element",
"un élément",
"cette action",
"Validation requise",
"cible inconnue",
],
)
def test_blacklist_refuses_generic_formulations(bad_phrase):
message = _raw_pause(vu=f"je vois {bad_phrase}")
result = validate_supervised_pause_message(message)
assert not result.valid
assert "generic_phrase" in {issue.code for issue in result.issues}
@pytest.mark.parametrize(
"technical_text",
[
"action_click_12ab34",
"replay_9f8e7d6c",
"session_id",
"target_spec.by_text",
"550e8400-e29b-41d4-a716-446655440000",
"a3f6c9d8e1b24567",
],
)
def test_refuses_raw_technical_identifiers(technical_text):
message = _raw_pause(attendu=f"voir le dossier patient apres {technical_text}")
assert "technical_identifier" in _issue_codes(message) or "technical_field" in _issue_codes(message)
@pytest.mark.parametrize(
"technical_text",
[
"(123, 456)",
"x=120 y=340",
"340px",
"score=0.87",
"confidence=0.91",
"similarité=0.42",
],
)
def test_refuses_pixels_and_raw_scores(technical_text):
message = _raw_pause(vu=f"la page Aiva avec {technical_text}")
codes = _issue_codes(message)
assert "raw_coordinates" in codes or "raw_score" in codes
@pytest.mark.parametrize(
"technical_english",
[
"target_not_found",
"no_screen_change",
"wrong_window",
"validation required",
"retry",
"screenshot",
],
)
def test_refuses_technical_english(technical_english):
message = _raw_pause(vu=f"le message {technical_english} est affiche")
assert "technical_english" in _issue_codes(message)
def test_refuses_raw_english_instruction():
message = _raw_pause(demande="please click the target button")
codes = _issue_codes(message)
assert "technical_english" in codes
assert "not_actionable" in codes
def test_refuses_messages_without_four_required_lines():
result = validate_supervised_pause_message("Je ne trouve pas le dossier patient.")
assert not result.valid
assert "invalid_structure" in {issue.code for issue in result.issues}
def test_refuses_wrong_label_order():
message = "\n".join(
[
"J'attendais : voir la fiche patient",
"J'essaie de : ouvrir le dossier patient",
"Je vois : la page d'accueil",
"Peux-tu : ouvrir le dossier puis me rendre la main",
]
)
assert "invalid_structure" in _issue_codes(message)
def test_demande_must_be_actionable_in_french():
message = "\n".join(
[
"J'essaie de : ouvrir le dossier patient",
"J'attendais : voir la fiche patient ouverte",
"Je vois : la page d'accueil Aiva Urgence",
"Peux-tu : merci beaucoup",
]
)
assert "not_actionable" in _issue_codes(message)
def test_visible_message_validator_accepts_clear_french_actionable_text():
message = (
"Je ne trouve pas le dossier patient dans Aiva Urgence. "
"Peux-tu ouvrir le dossier puis me rendre la main ?"
)
assert validate_visible_message(message).valid
def test_formatter_raises_instead_of_emitting_generic_message():
with pytest.raises(MessageContractError):
format_supervised_pause_message(
intention="faire cette action",
attendu="validation requise",
vu="un element",
demande="corriger",
)
def test_formatter_raises_on_too_short_request():
with pytest.raises(MessageContractError):
format_supervised_pause_message(
intention="ouvrir le dossier patient dans Aiva Urgence",
attendu="voir la fiche du patient ouverte",
vu="la page d'accueil Aiva Urgence",
demande="corriger",
)
def test_coerce_turns_legacy_validation_required_into_structured_pause():
message = coerce_supervised_pause_message("Validation requise")
assert validate_supervised_pause_message(message).valid
assert "Validation requise" not in message
assert message.splitlines()[0].startswith("J'essaie de :")
def test_coerce_keeps_clear_legacy_request_as_demande():
message = coerce_supervised_pause_message(
"Valider le dossier patient avant enregistrement",
intention="enregistrer le dossier patient",
attendu="avoir ton accord avant l'enregistrement",
vu="le formulaire patient est pret a etre enregistre",
)
assert validate_supervised_pause_message(message).valid
assert "Valider le dossier patient avant enregistrement" in message
def test_warn_visible_message_logs_without_modifying_message(caplog):
raw = "Validation requise"
returned = warn_visible_message(raw, source="unit.raw")
assert returned == raw
assert "invalid_message source=unit.raw" in caplog.text
assert "generic_phrase" in caplog.text
def test_warn_visible_message_accepts_supervised_pause_without_log(caplog):
message = _valid_pause()
returned = warn_visible_message(
message,
source="unit.final",
supervised_pause=True,
)
assert returned == message
assert "invalid_message" not in caplog.text
def test_refuses_overlong_fields_and_messages():
long_field = "ouvrir " + ("le dossier patient " * 45)
assert len(long_field) > MAX_FIELD_CHARS
message = "\n".join(
[
f"J'essaie de : {long_field}",
"J'attendais : voir la fiche patient ouverte",
"Je vois : la page d'accueil Aiva Urgence",
"Peux-tu : ouvrir le dossier patient puis me rendre la main",
]
)
codes = _issue_codes(message)
assert "field_too_long" in codes
assert "message_too_long" in codes