- Ajoute clic/survol/affichage-ecran comme declencheurs, et surbrillance, video, son, visibilite, indication, attendre comme actions, utilisables aussi bien par l'editeur manuel (menu lateral Objets/Ecran) que par Ruby (IA), avec blocs deplacables/supprimables dans une chaine. - Corrige plusieurs variantes du bug "impossible de poser un objet hors du champ de la camera" (troncature du chainage d'actions a 4 maillons, fond importe pose a 128x128 au lieu de sa taille reelle, decalage du fond au vrai glisser-depose, redimensionnement manuel jamais propage au monde). - Ajoute un vrai glisser-depose depuis la galerie vers la scene, la gestion complete de "Mes assets" (sous-sections Fonds/Decors/Sons/ Videos, suppression, reclassement fond<->decor sans re-upload). - Ajoute l'upload de son (limite 3 min) et de video (MP4 uniquement, limite 5 min), avec validation de la duree reelle du fichier, et une replique audio optionnelle dans une bulle de dialogue. - Fixe la taille de pose d'un objet/decor importe a 200x200 avec une boite de collision de 150x150. - Filtre le selecteur de fichier des actions son/video par type reel. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
420 lines
22 KiB
Python
420 lines
22 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_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)
|