Files
Forge-Engine/tests/test_ai_tools.py
T
williamandClaude Sonnet 5 7db4803b93
Build and deploy / test-python (push) Successful in 12m10s
Build and deploy / test-js (push) Successful in 1m27s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Ajoute la fonctionnalite quiz autonome/plein ecran a la boite a quiz
Introduit la double categorie de modeles (boite de dialogue / page de
quiz plein ecran) avec plein ecran, minuteur, score integre et ecran de
resultat pour les modeles page ; ajoute les modeles "Manga" (boite et
page) et "Classique" (page), pilotables aussi par l'assistant IA Ruby.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 15:07:23 +02:00

463 lines
24 KiB
Python

"""Couche outils IA (voir plan Phase 1, §4 — ai/tools.py) : chaque
dispatch doit appeler DIRECTEMENT la fonction screens.*/db.* existante
(jamais une structure parallèle), et les schémas doivent suivre les
constantes SOURCE plutôt qu'une copie littérale (principe validé avec
l'utilisateur : une nouvelle valeur d'énum doit suivre automatiquement)."""
import ai
import auth
import db
import screens
from ai.tools import TOOLS, dispatch_tool
def _tool(name):
return next(t for t in TOOLS if t["name"] == name)
def _create_jeu2d_game(client, tmp_game_slug_cleanup, name="pytest_ai_tools"):
resp = client.post("/games/new", data={"name": name}, follow_redirects=False)
slug = tmp_game_slug_cleanup(resp.headers["Location"].rstrip("/").split("/")[-1])
screen_id = screens.create_screen(slug, "Scène 1", kind="jeu_2d")
return slug, screen_id
def test_dispatch_add_scene_object_calls_the_real_function(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
result = dispatch_tool(slug, screen_id, 1, "add_scene_object", {"kind": "decor", "image_url": "/assets/1/x.png"})
obj = screens.get_scene_object(slug, result["object_id"])
assert obj["kind"] == "decor"
assert obj["attributes"]["src"] == "/assets/1/x.png"
def test_dispatch_add_scene_object_with_background_slug_uses_the_library_image(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
with client.session_transaction() as sess:
admin_id = sess["user_id"]
bg_slug = next(iter(screens.BACKGROUND_LIBRARY))
result = dispatch_tool(slug, screen_id, admin_id, "add_scene_object", {"kind": "fond", "background_slug": bg_slug})
obj = screens.get_scene_object(slug, result["object_id"])
assert obj["attributes"]["src"] == screens.BACKGROUND_LIBRARY[bg_slug]["url"]
def test_dispatch_add_scene_object_raises_for_an_unknown_background_slug(client, tmp_game_slug_cleanup):
"""Bug corrigé : un slug inventé/mal orthographié par Claude était
ignoré en silence (screens.add_scene_object, pensé pour un <select>
HTML qui ne propose que des valeurs valides) — Ruby annonçait alors
un fond posé qui n'apparaissait jamais en jeu. Lever une erreur ici
la remonte comme résultat d'outil (voir ai/chat.py), visible par
Claude plutôt qu'un faux succès."""
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
with client.session_transaction() as sess:
admin_id = sess["user_id"]
try:
dispatch_tool(slug, screen_id, admin_id, "add_scene_object", {"kind": "fond", "background_slug": "ne-existe-pas"})
assert False, "devrait lever ValueError"
except ValueError:
pass
def test_dispatch_add_scene_object_raises_for_an_unknown_forge_character(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
with client.session_transaction() as sess:
admin_id = sess["user_id"]
try:
dispatch_tool(slug, screen_id, admin_id, "add_scene_object", {"kind": "personnage", "forge_character": "ne-existe-pas"})
assert False, "devrait lever ValueError"
except ValueError:
pass
def test_dispatch_add_scene_object_forbids_admin_only_background_for_a_non_admin(client, user_client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
with user_client.session_transaction() as sess:
user_id = sess["user_id"]
bg_slug = next(iter(screens.ADMIN_ONLY_BACKGROUND_SLUGS))
result = dispatch_tool(slug, screen_id, user_id, "add_scene_object", {"kind": "fond", "background_slug": bg_slug})
obj = screens.get_scene_object(slug, result["object_id"])
# Retombe sur le comportement par défaut (aucun src) plutôt que de
# poser l'image admin-only — jamais une erreur qui casse le tour de chat.
assert "src" not in obj["attributes"]
def test_dispatch_add_scene_object_forbids_admin_only_character_for_a_non_admin(client, user_client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
with user_client.session_transaction() as sess:
user_id = sess["user_id"]
char_slug = next(iter(screens.ADMIN_ONLY_CHARACTER_SLUGS))
result = dispatch_tool(slug, screen_id, user_id, "add_scene_object", {"kind": "personnage", "forge_character": char_slug})
obj = screens.get_scene_object(slug, result["object_id"])
import json
assert json.loads(obj["attributes"]["_personnage_data"])["forge_character"] != char_slug
def test_add_scene_object_forge_character_enum_matches_the_source_constant():
schema = _tool("add_scene_object")["input_schema"]["properties"]["forge_character"]
assert set(schema["enum"]) == set(screens.SPRITE_LIBRARY)
def test_add_scene_object_background_slug_enum_matches_the_source_constant():
schema = _tool("add_scene_object")["input_schema"]["properties"]["background_slug"]
assert set(schema["enum"]) == set(screens.BACKGROUND_LIBRARY)
def test_dispatch_set_object_geometry_updates_position_and_size(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
object_id = screens.add_scene_object(slug, screen_id, kind="decor")
dispatch_tool(slug, screen_id, 1, "set_object_geometry", {"object_id": object_id, "x": 10, "y": 20, "width": 64, "height": 96})
obj = screens.get_scene_object(slug, object_id)
assert (obj["x"], obj["y"], obj["width"], obj["height"]) == (10, 20, 64, 96)
def test_dispatch_set_object_name_calls_the_real_function(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
object_id = screens.add_scene_object(slug, screen_id, kind="personnage")
dispatch_tool(slug, screen_id, 1, "set_object_name", {"object_id": object_id, "name": "Aka"})
obj = screens.get_scene_object(slug, object_id)
assert obj["name"] == "Aka"
def test_dispatch_set_object_geometry_clamps_a_position_outside_the_camera(client, tmp_game_slug_cleanup):
"""Bug corrigé à plusieurs reprises malgré des instructions explicites
ET un avertissement dans le résultat de l'outil : l'IA n'arrivait
toujours pas à se replacer correctement elle-même. Le moteur RAMÈNE
maintenant automatiquement la position dans le cadre (voir
_clamp_to_camera, ai/tools.py) — garanti dès le premier appel, sans
compter sur une correction ultérieure de l'IA."""
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup) # écran 960x540 par défaut
object_id = screens.add_scene_object(slug, screen_id, kind="personnage")
result = dispatch_tool(slug, screen_id, 1, "set_object_geometry", {"object_id": object_id, "x": 950, "y": 550, "width": 200, "height": 300})
assert "note" in result
obj = screens.get_scene_object(slug, object_id)
assert obj["x"] + obj["width"] <= 960
assert obj["y"] + obj["height"] <= 540
def test_dispatch_set_object_geometry_no_note_when_already_inside_the_camera(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
object_id = screens.add_scene_object(slug, screen_id, kind="personnage")
result = dispatch_tool(slug, screen_id, 1, "set_object_geometry", {"object_id": object_id, "x": 400, "y": 200, "width": 128, "height": 128})
assert "note" not in result
obj = screens.get_scene_object(slug, object_id)
assert (obj["x"], obj["y"]) == (400, 200)
def test_dispatch_set_object_geometry_never_clamps_a_fond_bigger_than_the_camera(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
object_id = screens.add_scene_object(slug, screen_id, kind="fond")
result = dispatch_tool(slug, screen_id, 1, "set_object_geometry", {"object_id": object_id, "x": 0, "y": 0, "width": 2000, "height": 1000})
assert "note" not in result
obj = screens.get_scene_object(slug, object_id)
assert (obj["width"], obj["height"]) == (2000, 1000)
def test_dispatch_set_object_role_updates_the_role(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
object_id = screens.add_scene_object(slug, screen_id, kind="personnage")
dispatch_tool(slug, screen_id, 1, "set_object_role", {"object_id": object_id, "role": "ennemie"})
obj = screens.get_scene_object(slug, object_id)
assert screens.resolve_personnage_role(obj) == "ennemie"
def test_dispatch_set_object_collision_updates_the_settings(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
object_id = screens.add_scene_object(slug, screen_id, kind="decor")
dispatch_tool(slug, screen_id, 1, "set_object_collision", {"object_id": object_id, "enabled": False, "shape": "cercle"})
obj = screens.get_scene_object(slug, object_id)
settings = screens.resolve_collision_settings(obj)
assert settings["enabled"] is False
assert settings["shape"] == "cercle"
def test_dispatch_set_quiz_box_config_updates_the_settings(client, tmp_game_slug_cleanup):
"""Demande explicite : "Ruby dois pouvoir aussi piloter ces réglages"
(plein écran/minuteur/modèle d'une "❓ Boîte à quiz", voir
screens/rendering/quiz_box_config.py) — pas seulement l'éditeur
manuel."""
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
object_id = screens.add_scene_object(slug, screen_id, kind="quiz_box")
dispatch_tool(slug, screen_id, 1, "set_quiz_box_config", {
"object_id": object_id, "fullscreen": True, "timer_mode": "question",
"timer_seconds": 15, "dialog_template": "defaut", "page_template": "manga",
})
obj = screens.get_scene_object(slug, object_id)
config = screens.resolve_quiz_box_config(obj)
assert config == {
"fullscreen": True, "timer_mode": "question", "timer_seconds": 15,
"dialog_template": "defaut", "page_template": "manga",
}
def test_dispatch_set_quiz_box_config_defaults_to_no_fullscreen_no_timer(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
object_id = screens.add_scene_object(slug, screen_id, kind="quiz_box")
dispatch_tool(slug, screen_id, 1, "set_quiz_box_config", {"object_id": object_id})
obj = screens.get_scene_object(slug, object_id)
config = screens.resolve_quiz_box_config(obj)
assert config == screens.DEFAULT_QUIZ_BOX_CONFIG
def test_dispatch_create_global_variable_calls_db_directly(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
result = dispatch_tool(slug, screen_id, 1, "create_global_variable", {"name": "score_ia", "var_type": "nombre_entier"})
assert result["variable_id"]
names = [v["name"] for v in db.list_global_variables(slug)]
assert "score_ia" in names
def test_dispatch_set_collision_rules_sanitizes_and_stores(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
object_id = screens.add_scene_object(slug, screen_id, kind="personnage")
rules = [{"trigger": "collision", "action": {"type": "dialogue", "dialogue": {"id": "d_1", "lines": []}}}]
dispatch_tool(slug, screen_id, 1, "set_collision_rules", {"object_id": object_id, "rules": rules})
obj = screens.get_scene_object(slug, object_id)
assert screens.resolve_collision_rules(obj)[0]["action"]["type"] == "dialogue"
def test_dispatch_append_action_to_trigger_extends_an_existing_chain(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
object_id = screens.add_scene_object(slug, screen_id, kind="personnage")
screens.set_scene_object_collision_rules(slug, object_id, screens.sanitize_collision_rules([
{"trigger": "collision", "action": {"type": "dialogue", "dialogue": {"id": "d_1", "lines": []}}},
]))
result = dispatch_tool(slug, screen_id, 1, "append_action_to_trigger", {
"object_id": object_id, "after_id": "d_1",
"action": {"type": "variable", "target_variable": "score_ia", "data_operation": "incrementer", "data_value": "1"},
})
assert result["ok"] is True
obj = screens.get_scene_object(slug, object_id)
assert screens.resolve_collision_rules(obj)[0]["action"]["then"]["target_variable"] == "score_ia"
def test_dispatch_set_collision_rules_accepts_clic_and_survol_triggers(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
object_id = screens.add_scene_object(slug, screen_id, kind="decor")
rules = [{"trigger": "clic", "action": {"type": "surbrillance", "object_id": object_id, "valeur": "toggle"}}]
dispatch_tool(slug, screen_id, 1, "set_collision_rules", {"object_id": object_id, "rules": rules})
obj = screens.get_scene_object(slug, object_id)
assert screens.resolve_collision_rules(obj) == rules
def test_dispatch_set_screen_triggers_sanitizes_and_stores(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
rules = [{"trigger": "affichage", "action": {"type": "video", "asset_url": "/assets/1/intro.mp4", "mode": "plein_ecran"}}]
result = dispatch_tool(slug, screen_id, 1, "set_screen_triggers", {"rules": rules})
assert result["ok"] is True
screen = screens.get_screen(slug, screen_id)
assert screens.resolve_screen_triggers(screen) == rules
def test_dispatch_append_action_to_screen_trigger_extends_an_existing_chain(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
screens.set_screen_triggers(slug, screen_id, screens.sanitize_screen_triggers([
{"trigger": "affichage", "action": {"type": "dialogue", "dialogue": {"id": "d_1", "lines": []}}},
]))
result = dispatch_tool(slug, screen_id, 1, "append_action_to_screen_trigger", {
"after_id": "d_1",
"action": {"type": "variable", "target_variable": "score_ia", "data_operation": "incrementer", "data_value": "1"},
})
assert result["ok"] is True
screen = screens.get_screen(slug, screen_id)
assert screens.resolve_screen_triggers(screen)[0]["action"]["then"]["target_variable"] == "score_ia"
def test_dispatch_remove_trigger_action_removes_a_leaf(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
object_id = screens.add_scene_object(slug, screen_id, kind="decor")
screens.set_scene_object_collision_rules(slug, object_id, screens.sanitize_collision_rules([
{"trigger": "collision", "action": {"type": "dialogue", "dialogue": {"id": "d_1", "lines": []}}},
]))
result = dispatch_tool(slug, screen_id, 1, "remove_trigger_action", {"object_id": object_id, "leaf_id": "d_1"})
assert result["ok"] is True
obj = screens.get_scene_object(slug, object_id)
assert screens.resolve_collision_rules(obj) == []
def test_dispatch_move_trigger_action_swaps_a_leaf_up(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
object_id = screens.add_scene_object(slug, screen_id, kind="decor")
screens.set_scene_object_collision_rules(slug, object_id, screens.sanitize_collision_rules([{
"trigger": "collision",
"action": {
"type": "variable", "id": "v_1", "target_variable": "score", "data_operation": "incrementer", "data_value": "1",
"then": {"type": "dialogue", "dialogue": {"id": "d_1", "lines": []}},
},
}]))
result = dispatch_tool(slug, screen_id, 1, "move_trigger_action", {"object_id": object_id, "leaf_id": "d_1", "direction": "up"})
assert result["ok"] is True
obj = screens.get_scene_object(slug, object_id)
assert screens.resolve_collision_rules(obj)[0]["action"]["type"] == "dialogue"
def test_dispatch_remove_screen_trigger_action_removes_a_leaf(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
screens.set_screen_triggers(slug, screen_id, screens.sanitize_screen_triggers([
{"trigger": "affichage", "action": {"type": "dialogue", "dialogue": {"id": "d_1", "lines": []}}},
]))
result = dispatch_tool(slug, screen_id, 1, "remove_screen_trigger_action", {"leaf_id": "d_1"})
assert result["ok"] is True
screen = screens.get_screen(slug, screen_id)
assert screens.resolve_screen_triggers(screen) == []
def test_dispatch_move_screen_trigger_action_swaps_a_leaf_up(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
screens.set_screen_triggers(slug, screen_id, screens.sanitize_screen_triggers([{
"trigger": "affichage",
"action": {
"type": "attendre", "id": "a_1", "data_value": "2",
"then": {"type": "dialogue", "dialogue": {"id": "d_1", "lines": []}},
},
}]))
result = dispatch_tool(slug, screen_id, 1, "move_screen_trigger_action", {"leaf_id": "d_1", "direction": "up"})
assert result["ok"] is True
screen = screens.get_screen(slug, screen_id)
assert screens.resolve_screen_triggers(screen)[0]["action"]["type"] == "dialogue"
def test_dispatch_tool_raises_for_an_unknown_tool_name(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
try:
dispatch_tool(slug, screen_id, 1, "does_not_exist", {})
assert False, "devrait lever KeyError"
except KeyError:
pass
class _FakeDownloadResponse:
"""Mirroir minimal de requests.Response — voir
test_dispatch_add_generated_image_* ci-dessous (jamais de vrai appel
réseau, ni vers Scenario ni pour télécharger l'image résultante)."""
content = b"fake-generated-image-bytes"
def raise_for_status(self):
pass
def test_dispatch_add_generated_image_saves_to_my_assets_and_poses_it(client, tmp_game_slug_cleanup, monkeypatch):
"""Scenario (mocké) -> "Mes assets" (source="ia") -> posée sur
l'écran via LE MÊME add_scene_object que Phase 1 — jamais un chemin
parallèle (voir plan Phase 2, §4)."""
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
with client.session_transaction() as sess:
user_id = sess["user_id"]
monkeypatch.setattr("ai.tools.generate_image_url", lambda prompt: "https://scenario.example/image.png")
monkeypatch.setattr("ai.tools.requests.get", lambda url, timeout=None: _FakeDownloadResponse())
result = dispatch_tool(slug, screen_id, user_id, "add_generated_image", {"kind": "decor", "prompt": "un bureau moderne"})
obj = screens.get_scene_object(slug, result["object_id"])
assert obj["kind"] == "decor"
assert f"/assets/{user_id}/" in obj["attributes"]["src"]
assets = auth.list_user_assets(user_id)
assert any(a["id"] == result["asset_id"] and a["source"] == "ia" for a in assets)
def test_dispatch_add_generated_image_surfaces_scenario_errors_as_a_tool_error(client, tmp_game_slug_cleanup, monkeypatch):
"""Le chat (ai/chat.py) attrape déjà les exceptions de dispatch_tool
au niveau de la boucle — mais un test direct de la propagation
évite une régression silencieuse si ce comportement change."""
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
def _raise(prompt):
raise ai.ScenarioNotConfiguredError("clé absente")
monkeypatch.setattr("ai.tools.generate_image_url", _raise)
try:
dispatch_tool(slug, screen_id, 1, "add_generated_image", {"kind": "decor", "prompt": "un bureau"})
assert False, "devrait lever ScenarioNotConfiguredError"
except ai.ScenarioNotConfiguredError:
pass
# ---------- Anti-dérive : les énums viennent des constantes SOURCE ----------
# (voir screens/rendering/collision_rules.py, screens/labels/data_operations.py,
# db/constants.py) — jamais recopiées en dur dans ai/tools.py. Si ce test
# échoue après l'ajout d'une nouvelle valeur côté moteur, c'est que
# quelqu'un a remplacé la référence par une copie littérale : à corriger
# dans ai/tools.py, pas dans ce test.
def test_set_collision_rules_action_type_enum_matches_the_source_constant():
from screens.rendering.collision_rules import ACTION_TYPES
action_schema = _tool("set_collision_rules")["input_schema"]["properties"]["rules"]["items"]["properties"]["action"]
assert action_schema["properties"]["type"]["enum"] == list(ACTION_TYPES)
def test_set_collision_rules_trigger_enum_matches_the_source_constant():
from screens.rendering.collision_rules import TRIGGER_TYPES
rules_schema = _tool("set_collision_rules")["input_schema"]["properties"]["rules"]["items"]
assert rules_schema["properties"]["trigger"]["enum"] == list(TRIGGER_TYPES)
def test_set_screen_triggers_trigger_enum_matches_the_source_constant():
from screens.rendering.screen_triggers import TRIGGER_TYPES_SCREEN
rules_schema = _tool("set_screen_triggers")["input_schema"]["properties"]["rules"]["items"]
assert rules_schema["properties"]["trigger"]["enum"] == list(TRIGGER_TYPES_SCREEN)
def test_set_collision_rules_video_mode_enum_matches_the_source_constant():
from screens.labels.video_modes import VIDEO_MODE_LABELS
action_schema = _tool("set_collision_rules")["input_schema"]["properties"]["rules"]["items"]["properties"]["action"]
assert set(action_schema["properties"]["mode"]["enum"]) == set(VIDEO_MODE_LABELS)
def test_set_collision_rules_condition_operator_enum_matches_the_source_constant():
from screens.rendering.collision_rules import CONDITION_OPERATOR_KEYS
action_schema = _tool("set_collision_rules")["input_schema"]["properties"]["rules"]["items"]["properties"]["action"]
assert set(action_schema["properties"]["operateur"]["enum"]) == CONDITION_OPERATOR_KEYS
def test_set_collision_rules_data_operation_enum_matches_the_source_constant():
from screens.labels.data_operations import DATA_OPERATION_LABELS
action_schema = _tool("set_collision_rules")["input_schema"]["properties"]["rules"]["items"]["properties"]["action"]
assert set(action_schema["properties"]["data_operation"]["enum"]) == set(DATA_OPERATION_LABELS)
def test_append_action_to_trigger_leaf_type_enum_matches_the_source_constant():
from screens.rendering.collision_rules import LEAF_ACTION_TYPES
leaf_schema = _tool("append_action_to_trigger")["input_schema"]["properties"]["action"]
assert leaf_schema["properties"]["type"]["enum"] == list(LEAF_ACTION_TYPES)
def test_append_action_to_trigger_dialogue_description_documents_reward_amount():
"""Bug corrigé : la forme d'une ligne de dialogue/question n'était pas
décrite au tool, jusqu'à ce que Ruby invente une variable séparée
pour un score de quiz au lieu d'utiliser reward_amount (déjà
automatique, voir db/dialogue_lines.py). Vérifie que la RÉFÉRENCE aux
valeurs valides de reward_type est bien utilisée (pas une copie)."""
from db.dialogue_lines import QUESTION_REWARD_TYPES
leaf_schema = _tool("append_action_to_trigger")["input_schema"]["properties"]["action"]
dialogue_desc = leaf_schema["properties"]["dialogue"]["description"]
assert "reward_amount" in dialogue_desc
assert all(t in dialogue_desc for t in QUESTION_REWARD_TYPES)
def test_create_global_variable_var_type_enum_matches_the_source_constant():
schema = _tool("create_global_variable")["input_schema"]["properties"]["var_type"]
assert set(schema["enum"]) == set(db.GLOBAL_VARIABLE_TYPES)
def test_set_object_role_enum_matches_the_source_constant():
schema = _tool("set_object_role")["input_schema"]["properties"]["role"]
assert schema["enum"] == list(screens.PERSONNAGE_ROLES)
def test_set_object_collision_shape_enum_matches_the_source_constant():
schema = _tool("set_object_collision")["input_schema"]["properties"]["shape"]
assert schema["enum"] == list(screens.COLLISION_SHAPES)
def test_set_quiz_box_config_timer_mode_enum_matches_the_source_constant():
schema = _tool("set_quiz_box_config")["input_schema"]["properties"]["timer_mode"]
assert schema["enum"] == list(screens.TIMER_MODES)
def test_set_quiz_box_config_dialog_template_enum_matches_the_source_constant():
schema = _tool("set_quiz_box_config")["input_schema"]["properties"]["dialog_template"]
assert schema["enum"] == list(screens.QUIZ_BOX_DIALOG_TEMPLATES)
def test_set_quiz_box_config_page_template_enum_matches_the_source_constant():
schema = _tool("set_quiz_box_config")["input_schema"]["properties"]["page_template"]
assert schema["enum"] == list(screens.QUIZ_BOX_PAGE_TEMPLATES)