"""Tests for the launcher — no GUI/network. The launcher only seeds config and applies a pre-staged update (downloading lives in services.installer/the app).""" import json import os import shutil import sys import tempfile import unittest from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent / "src")) import launcher from services import installer class _Base(unittest.TestCase): def setUp(self): self.tmp = Path(tempfile.mkdtemp()) self.root = self.tmp / "install" self.app = self.root / "app" (self.app / "config").mkdir(parents=True) (self.app / "CMBot.exe").write_text("OLD-1.0.0", encoding="ascii") (self.app / "version.txt").write_text("1.0.0", encoding="ascii") (self.app / "config" / "app_config.json").write_text( json.dumps({"update_source": "http://x"}), encoding="utf-8") (self.app / "config" / "templates.json").write_text("{}", encoding="utf-8") self.data = self.tmp / "data" self._prev = os.environ.get("CMBOT_DATA_DIR") os.environ["CMBOT_DATA_DIR"] = str(self.data) def tearDown(self): if self._prev is None: os.environ.pop("CMBOT_DATA_DIR", None) else: os.environ["CMBOT_DATA_DIR"] = self._prev shutil.rmtree(str(self.tmp), ignore_errors=True) def _stage(self, version, body): s = installer.staged_app(self.root) s.mkdir(parents=True, exist_ok=True) (s / "CMBot.exe").write_text(body, encoding="ascii") (s / "version.txt").write_text(version, encoding="ascii") class TestSeed(_Base): def test_seed_copies_when_missing(self): launcher.seed_defaults(self.app, self.data) self.assertTrue((self.data / "config" / "app_config.json").exists()) self.assertTrue((self.data / "config" / "templates.json").exists()) def test_seed_does_not_overwrite(self): (self.data / "config").mkdir(parents=True) (self.data / "config" / "app_config.json").write_text('{"update_source":"USER"}', encoding="utf-8") launcher.seed_defaults(self.app, self.data) kept = json.loads((self.data / "config" / "app_config.json").read_text(encoding="utf-8")) self.assertEqual(kept["update_source"], "USER") class TestRun(_Base): def test_applies_staged_update_then_launches(self): self._stage("1.1.0", "NEW-1.1.0") rc = launcher.run(self.root, no_launch=True) self.assertEqual(rc, 0) self.assertEqual((self.app / "version.txt").read_text(encoding="ascii"), "1.1.0") self.assertEqual((self.app / "CMBot.exe").read_text(encoding="ascii"), "NEW-1.1.0") self.assertEqual((installer.old_dir(self.root) / "version.txt").read_text(encoding="ascii"), "1.0.0") def test_no_staged_just_launches(self): rc = launcher.run(self.root, no_launch=True) self.assertEqual(rc, 0) self.assertEqual((self.app / "version.txt").read_text(encoding="ascii"), "1.0.0") self.assertFalse(installer.old_dir(self.root).exists()) def test_seeds_config_into_data_root(self): launcher.run(self.root, no_launch=True) self.assertTrue((self.data / "config" / "app_config.json").exists()) def test_missing_exe_returns_error(self): (self.app / "CMBot.exe").unlink() rc = launcher.run(self.root, no_launch=True) self.assertEqual(rc, 1) if __name__ == "__main__": unittest.main()