"""End-to-end tests for the dependency-free EQL spell CLI.""" from __future__ import annotations import json import shutil import subprocess import sys import tempfile import unittest from pathlib import Path ROOT = Path(__file__).resolve().parents[1] CLI = ROOT / "scripts" / "eql-spells" FIXTURE = ROOT / "tests" / "fixtures" / "eql-spells" class EqlSpellsCliTests(unittest.TestCase): def setUp(self) -> None: self.temporary = tempfile.TemporaryDirectory() self.work = Path(self.temporary.name) self.game = self.work / "game" shutil.copytree(FIXTURE, self.game) self.db = self.work / "cache" / "spells.sqlite" def tearDown(self) -> None: self.temporary.cleanup() def run_cli(self, *arguments: str) -> subprocess.CompletedProcess[str]: return subprocess.run( [sys.executable, str(CLI), "--game-path", str(self.game), "--db", str(self.db), *arguments], cwd=ROOT, text=True, capture_output=True, check=False, ) def json_stdout(self, *arguments: str) -> dict: result = self.run_cli(*arguments) self.assertEqual(result.returncode, 0, result.stderr) return json.loads(result.stdout) def test_build_and_exact_lookup_by_id_and_name(self) -> None: built = self.json_stdout("build") self.assertEqual(built["built"], {"descriptions": 3, "messages": 3, "spells": 3}) self.assertTrue(self.db.is_file()) by_id = self.json_stdout("get", "1") self.assertEqual(by_id["spell"]["name"], "Fire Bolt") self.assertEqual(by_id["spell"]["description"], "Launches a bolt of fire at your target.") self.assertEqual(by_id["spell"]["messages"]["caster_me"], "You cast Fire Bolt.") self.assertIn("fingerprint", by_id["source"]) by_name = self.json_stdout("get", "water breathing") self.assertEqual(by_name["spell"]["id"], 2) def test_find_search_and_raw_output(self) -> None: found = self.json_stdout("find", "fire") self.assertEqual([spell["name"] for spell in found["results"]], ["Fire Bolt", "Fireball"]) searched = self.json_stdout("search", "breathing underwater") self.assertEqual([spell["id"] for spell in searched["results"]], [2]) raw = self.json_stdout("get", "1", "--raw") self.assertEqual(raw["spell"]["raw"]["line"], "1^Fire Bolt^0^alpha") self.assertEqual(raw["spell"]["raw"]["fields"], ["1", "Fire Bolt", "0", "alpha"]) def test_query_automatically_rebuilds_after_source_change(self) -> None: self.json_stdout("build") descriptions = self.game / "dbstr_us.txt" with descriptions.open("a", encoding="utf-8") as handle: handle.write("2^6^Lets your target breathe underwater indefinitely.^0^\n") result = self.run_cli("search", "indefinitely") self.assertEqual(result.returncode, 1) self.assertIn("Duplicate spell description", json.loads(result.stderr)["error"]["message"]) # Replace the existing description rather than adding a duplicate. descriptions.write_text( "1^6^Launches a bolt of fire at your target.^0^\n" "2^6^Lets your target breathe underwater indefinitely.^0^\n" "3^6^Launches a ball of fire at your target.^0^\n" "1^5^Fire^0^\n", encoding="utf-8", ) rebuilt = self.json_stdout("search", "indefinitely") self.assertTrue(rebuilt["index_rebuilt"]) self.assertEqual(rebuilt["results"][0]["id"], 2) def test_malformed_data_and_invalid_query_return_structured_errors(self) -> None: (self.game / "spells_us.txt").write_text("bad row\n", encoding="utf-8") malformed = self.run_cli("build") self.assertEqual(malformed.returncode, 1) self.assertEqual(json.loads(malformed.stderr)["error"]["code"], "data") shutil.copy2(FIXTURE / "spells_us.txt", self.game / "spells_us.txt") invalid_limit = self.run_cli("find", "fire", "--limit", "101") self.assertEqual(invalid_limit.returncode, 2) self.assertEqual(json.loads(invalid_limit.stderr)["error"]["code"], "usage") missing = self.run_cli("get", "999") self.assertEqual(missing.returncode, 1) self.assertEqual(json.loads(missing.stderr)["error"]["code"], "data") if __name__ == "__main__": unittest.main()