From d651cb830b0c28605a63fe4c20ab62ea478e71e4 Mon Sep 17 00:00:00 2001 From: anadoris007 Date: Mon, 20 Apr 2026 22:05:08 +0530 Subject: [PATCH] test(narrative): add end-to-end pipeline test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Full-pipeline test: fake simulation dir → actions.jsonl → two translate_round calls → assertions on beat persistence, character evolution, and translator offset advancement. Final test suite: 20/20 passing across action mapper, character engine, story store, translator (read + prose + orchestration), and E2E. Co-Authored-By: Claude Opus 4.6 (1M context) --- backend/tests/test_narrative_e2e.py | 72 +++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 backend/tests/test_narrative_e2e.py diff --git a/backend/tests/test_narrative_e2e.py b/backend/tests/test_narrative_e2e.py new file mode 100644 index 00000000..d374af87 --- /dev/null +++ b/backend/tests/test_narrative_e2e.py @@ -0,0 +1,72 @@ +"""End-to-end test: simulated actions.jsonl → translate → verify story + state.""" +import os +import json +from unittest.mock import patch + +from app.services.narrative.story_store import StoryStore +from app.services.narrative.narrative_translator import translate_round + + +def test_full_pipeline_from_fake_simulation(tmp_path): + sim_dir = str(tmp_path / "sim_e2e") + os.makedirs(os.path.join(sim_dir, "twitter")) + + actions_path = os.path.join(sim_dir, "twitter", "actions.jsonl") + actions = [ + {"round": 1, "agent_id": 1, "agent_name": "Elena", "action_type": "CREATE_POST", + "action_args": {"content": "The council must fall."}, "success": True, "timestamp": "t"}, + {"round": 1, "agent_id": 2, "agent_name": "Marcus", "action_type": "DISLIKE_POST", + "action_args": {"post_id": 1}, "success": True, "timestamp": "t"}, + {"event_type": "round_end", "round": 1, "timestamp": "t"}, + {"round": 2, "agent_id": 1, "agent_name": "Elena", "action_type": "REPOST", + "action_args": {}, "success": True, "timestamp": "t"}, + {"round": 2, "agent_id": 2, "agent_name": "Marcus", "action_type": "FOLLOW", + "action_args": {"target": 3}, "success": True, "timestamp": "t"}, + {"event_type": "round_end", "round": 2, "timestamp": "t"}, + ] + with open(actions_path, "w") as f: + for a in actions: + f.write(json.dumps(a) + "\n") + + store = StoryStore(sim_dir) + neutral_emotions = {k: 0.0 for k in ["anger", "fear", "joy", "sadness", "surprise"]} + store.save_characters([ + {"id": "1", "name": "Elena", + "emotional_state": {"current": {**neutral_emotions, "trust": 0.5}, "history": []}}, + {"id": "2", "name": "Marcus", + "emotional_state": {"current": {**neutral_emotions, "trust": 0.5}, "history": []}}, + ]) + + with patch("app.services.narrative.narrative_translator.call_llm") as mock_llm: + mock_llm.side_effect = [ + "Elena addressed the gathering. Marcus's face darkened.", + "Elena's message spread through the quarter like a spark.", + ] + beat1 = translate_round(sim_dir, "twitter", 1, "dark fantasy") + beat2 = translate_round(sim_dir, "twitter", 2, "dark fantasy") + + # Beats are correctly attributed and sequenced + assert beat1["round"] == 1 + assert beat2["round"] == 2 + assert "Elena" in beat1["characters"] + assert "Marcus" in beat1["characters"] + assert beat1["action_count"] == 2 + + # Both beats persisted + all_beats = store.get_all_beats() + assert len(all_beats) == 2 + assert all_beats[0]["prose"] == "Elena addressed the gathering. Marcus's face darkened." + + # Characters evolved per the emotional delta rules + chars = {c["name"]: c for c in store.load_characters()} + + # Marcus DISLIKE_POST'd in round 1 → anger should be > 0 (delta 0.08) + assert chars["Marcus"]["emotional_state"]["current"]["anger"] > 0.0 + # Marcus FOLLOW'd in round 2 → trust should have climbed above baseline (delta 0.08) + assert chars["Marcus"]["emotional_state"]["current"]["trust"] > 0.5 + # Elena CREATE_POST'd then REPOST'd → joy and surprise both bumped + assert chars["Elena"]["emotional_state"]["current"]["joy"] > 0.0 + assert chars["Elena"]["emotional_state"]["current"]["surprise"] > 0.0 + + # Translator state advanced — next call for round 3 would resume, not re-read + assert store.get_file_offset("twitter") > 0