""" Sprint 26 Tests: canonical appearance settings persist and legacy theme names map onto the new theme + skin system. """ import json, urllib.error, urllib.request import pathlib import sys from tests._pytest_port import BASE REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent if str(REPO_ROOT) not in sys.path: sys.path.insert(0, str(REPO_ROOT)) from api import config def get(path): with urllib.request.urlopen(BASE + path, timeout=10) as r: return json.loads(r.read()), r.status def post(path, body=None): data = json.dumps(body or {}).encode() req = urllib.request.Request(BASE + path, data=data, headers={"Content-Type": "application/json"}) try: with urllib.request.urlopen(req, timeout=10) as r: return json.loads(r.read()), r.status except urllib.error.HTTPError as e: return json.loads(e.read()), e.code # ── Theme settings ─────────────────────────────────────────────────────── def test_settings_default_theme(): """Default theme should be 'dark'.""" d, status = get("/api/settings") assert status == 200 assert d.get("theme") == "dark" def test_settings_set_theme_light_persists(): """Setting theme to 'light' should persist and round-trip.""" try: d, status = post("/api/settings", {"theme": "light"}) assert status == 200 d2, _ = get("/api/settings") assert d2.get("theme") == "light" finally: # Reset to dark post("/api/settings", {"theme": "dark"}) def test_settings_set_theme_light(): """Setting theme to 'light' should persist.""" try: post("/api/settings", {"theme": "light"}) d, _ = get("/api/settings") assert d.get("theme") == "light" finally: post("/api/settings", {"theme": "dark"}) def test_settings_set_theme_system(): """Setting theme to 'system' should persist.""" try: post("/api/settings", {"theme": "system"}) d, _ = get("/api/settings") assert d.get("theme") == "system" finally: post("/api/settings", {"theme": "dark"}) def test_settings_set_skin(): """Setting skin should persist.""" try: post("/api/settings", {"skin": "ares"}) d, _ = get("/api/settings") assert d.get("skin") == "ares" finally: post("/api/settings", {"skin": "default"}) def test_settings_set_skin_poseidon(): """Setting skin to 'poseidon' should persist.""" try: post("/api/settings", {"skin": "poseidon"}) d, _ = get("/api/settings") assert d.get("skin") == "poseidon" finally: post("/api/settings", {"skin": "default"}) def test_settings_legacy_theme_maps_to_dark_skin_pair(): """Legacy theme names should map to the closest supported theme + skin.""" try: d, status = post("/api/settings", {"theme": "slate"}) assert status == 200 d2, _ = get("/api/settings") assert d2.get("theme") == "dark" assert d2.get("skin") == "slate" finally: post("/api/settings", {"theme": "dark", "skin": "default"}) def test_settings_legacy_monokai_maps_to_sisyphus_skin(): """Monokai should migrate onto the closest supported accent skin.""" try: d, status = post("/api/settings", {"theme": "monokai"}) assert status == 200 d2, _ = get("/api/settings") assert d2.get("theme") == "dark" assert d2.get("skin") == "sisyphus" finally: post("/api/settings", {"theme": "dark", "skin": "default"}) def test_settings_unknown_theme_falls_back_to_dark_default(): """Unknown themes should normalize to a safe canonical appearance.""" try: d, status = post("/api/settings", {"theme": "my-custom-theme"}) assert status == 200 d2, _ = get("/api/settings") assert d2.get("theme") == "dark" assert d2.get("skin") == "default" finally: post("/api/settings", {"theme": "dark", "skin": "default"}) def test_settings_invalid_skin_falls_back_to_default(): """Unknown skin names should normalize back to the default accent.""" try: d, status = post("/api/settings", {"skin": "not-a-skin"}) assert status == 200 d2, _ = get("/api/settings") assert d2.get("skin") == "default" finally: post("/api/settings", {"skin": "default"}) def test_load_settings_normalizes_legacy_theme_from_file(monkeypatch, tmp_path): """Existing settings.json files with legacy theme names should normalize on load.""" settings_path = tmp_path / "settings.json" settings_path.write_text(json.dumps({"theme": "solarized"}), encoding="utf-8") monkeypatch.setattr(config, "SETTINGS_FILE", settings_path) loaded = config.load_settings() assert loaded["theme"] == "dark" assert loaded["skin"] == "poseidon" def test_theme_does_not_break_other_settings(): """Setting theme should not disturb other settings.""" d_before, _ = get("/api/settings") send_key_before = d_before.get("send_key") try: post("/api/settings", {"theme": "light"}) d_after, _ = get("/api/settings") assert d_after.get("send_key") == send_key_before assert d_after.get("theme") == "light" finally: post("/api/settings", {"theme": "dark"}) def test_theme_survives_round_trip(): """Theme set via POST should appear in subsequent GET.""" try: post("/api/settings", {"theme": "light"}) d, status = get("/api/settings") assert status == 200 assert d["theme"] == "light" finally: post("/api/settings", {"theme": "dark"})