Ajoute l'assistant IA "Ruby" (Claude + Scenario) et corrige plusieurs bugs de la scène
Intègre un chat IA capable de manipuler la scène via les mêmes fonctions que l'éditeur manuel (objets, variables, déclencheurs, images générées), avec conversations multiples par écran façon Claude. Corrige au passage le rafraîchissement pjax hors-ordre, l'onglet IA/déclencheurs vide après sélection d'un objet, la comparaison de booléens dans les conditions, et le blocage du glisser-déposer hors du cadre caméra après un redimensionnement de fond par l'IA. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
ab6635eee0
commit
b07b231a61
@@ -0,0 +1,121 @@
|
||||
"""Onglet "Générer avec l'IA" — plusieurs CONVERSATIONS par écran (CRUD,
|
||||
comme claude.ai, voir plan) : routes/ia/ia_chat.py + screens/ia/.
|
||||
ANTHROPIC_API_KEY est toujours absente en test (voir tests/conftest.py) :
|
||||
ces tests exercent donc le chemin de DÉGRADATION GRACIEUSE (message
|
||||
d'erreur clair, jamais une 500) pour l'envoi de message — pas un appel
|
||||
réel, quel que soit le .env local du poste."""
|
||||
import screens
|
||||
|
||||
|
||||
def _create_jeu2d_game(client, tmp_game_slug_cleanup, name="pytest_ia_chat"):
|
||||
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_create_and_list_conversations(client, tmp_game_slug_cleanup):
|
||||
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
|
||||
resp = client.post(f"/game/{slug}/screens/{screen_id}/ia-conversations")
|
||||
assert resp.status_code == 200
|
||||
conversation = resp.get_json()
|
||||
assert conversation["title"] == "Nouvelle conversation"
|
||||
|
||||
resp2 = client.get(f"/game/{slug}/screens/{screen_id}/ia-conversations")
|
||||
assert resp2.status_code == 200
|
||||
ids = [c["id"] for c in resp2.get_json()]
|
||||
assert conversation["id"] in ids
|
||||
|
||||
|
||||
def test_conversations_are_isolated_per_screen(client, tmp_game_slug_cleanup):
|
||||
slug, screen_id_1 = _create_jeu2d_game(client, tmp_game_slug_cleanup)
|
||||
screen_id_2 = screens.create_screen(slug, "Scène 2", kind="jeu_2d")
|
||||
client.post(f"/game/{slug}/screens/{screen_id_1}/ia-conversations")
|
||||
|
||||
resp = client.get(f"/game/{slug}/screens/{screen_id_2}/ia-conversations")
|
||||
assert resp.get_json() == []
|
||||
|
||||
|
||||
def test_delete_conversation_removes_it_from_the_list(client, tmp_game_slug_cleanup):
|
||||
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
|
||||
conversation = client.post(f"/game/{slug}/screens/{screen_id}/ia-conversations").get_json()
|
||||
|
||||
resp = client.delete(f"/game/{slug}/ia-conversations/{conversation['id']}")
|
||||
assert resp.status_code == 200
|
||||
|
||||
remaining = client.get(f"/game/{slug}/screens/{screen_id}/ia-conversations").get_json()
|
||||
assert all(c["id"] != conversation["id"] for c in remaining)
|
||||
|
||||
|
||||
def test_delete_unknown_conversation_404s(client, game):
|
||||
resp = client.delete(f"/game/{game}/ia-conversations/999999")
|
||||
assert resp.status_code == 404
|
||||
|
||||
|
||||
def test_send_message_persists_it_and_a_graceful_error_reply(client, tmp_game_slug_cleanup):
|
||||
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
|
||||
conversation = client.post(f"/game/{slug}/screens/{screen_id}/ia-conversations").get_json()
|
||||
|
||||
resp = client.post(
|
||||
f"/game/{slug}/ia-conversations/{conversation['id']}/messages", json={"message": "Génère un dialogue"}
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
body = resp.get_json()
|
||||
assert body["user_message"]["content"] == "Génère un dialogue"
|
||||
assert body["user_message"]["role"] == "user"
|
||||
assert body["assistant_message"]["role"] == "assistant"
|
||||
assert "Configuration IA incomplète" in body["assistant_message"]["content"]
|
||||
|
||||
|
||||
def test_send_message_rejects_an_empty_message(client, tmp_game_slug_cleanup):
|
||||
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
|
||||
conversation = client.post(f"/game/{slug}/screens/{screen_id}/ia-conversations").get_json()
|
||||
resp = client.post(f"/game/{slug}/ia-conversations/{conversation['id']}/messages", json={"message": " "})
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
def test_send_message_404s_for_an_unknown_conversation(client, game):
|
||||
resp = client.post(f"/game/{game}/ia-conversations/999999/messages", json={"message": "salut"})
|
||||
assert resp.status_code == 404
|
||||
|
||||
|
||||
def test_messages_history_is_persisted_and_ordered(client, tmp_game_slug_cleanup):
|
||||
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
|
||||
conversation = client.post(f"/game/{slug}/screens/{screen_id}/ia-conversations").get_json()
|
||||
|
||||
client.post(f"/game/{slug}/ia-conversations/{conversation['id']}/messages", json={"message": "premier"})
|
||||
client.post(f"/game/{slug}/ia-conversations/{conversation['id']}/messages", json={"message": "second"})
|
||||
|
||||
resp = client.get(f"/game/{slug}/ia-conversations/{conversation['id']}/messages")
|
||||
assert resp.status_code == 200
|
||||
messages = resp.get_json()
|
||||
user_contents = [m["content"] for m in messages if m["role"] == "user"]
|
||||
assert user_contents == ["premier", "second"]
|
||||
|
||||
|
||||
def test_messages_are_isolated_per_conversation(client, tmp_game_slug_cleanup):
|
||||
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
|
||||
conversation_1 = client.post(f"/game/{slug}/screens/{screen_id}/ia-conversations").get_json()
|
||||
conversation_2 = client.post(f"/game/{slug}/screens/{screen_id}/ia-conversations").get_json()
|
||||
|
||||
client.post(f"/game/{slug}/ia-conversations/{conversation_1['id']}/messages", json={"message": "pour la 1ère"})
|
||||
|
||||
resp = client.get(f"/game/{slug}/ia-conversations/{conversation_2['id']}/messages")
|
||||
assert resp.get_json() == []
|
||||
|
||||
|
||||
def test_first_user_message_renames_the_conversation(client, tmp_game_slug_cleanup):
|
||||
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
|
||||
conversation = client.post(f"/game/{slug}/screens/{screen_id}/ia-conversations").get_json()
|
||||
|
||||
client.post(f"/game/{slug}/ia-conversations/{conversation['id']}/messages", json={"message": "Crée un fond de bureau"})
|
||||
|
||||
conversations = client.get(f"/game/{slug}/screens/{screen_id}/ia-conversations").get_json()
|
||||
updated = next(c for c in conversations if c["id"] == conversation["id"])
|
||||
assert updated["title"] == "Crée un fond de bureau"
|
||||
|
||||
# Un deuxième message ne renomme plus (titre déjà dérivé du premier).
|
||||
client.post(f"/game/{slug}/ia-conversations/{conversation['id']}/messages", json={"message": "et une variable"})
|
||||
conversations2 = client.get(f"/game/{slug}/screens/{screen_id}/ia-conversations").get_json()
|
||||
updated2 = next(c for c in conversations2 if c["id"] == conversation["id"])
|
||||
assert updated2["title"] == "Crée un fond de bureau"
|
||||
Reference in New Issue
Block a user