281 lines
8.4 KiB
Python
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
|