"""Tests for AI-outfit config helpers in config_service (no GUI).""" 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 services.config_service as cs class TestOutfitConfigHelpers(unittest.TestCase): def setUp(self): self.tmp = Path(tempfile.mkdtemp()) self._env = os.environ.get("CMBOT_DATA_DIR") os.environ["CMBOT_DATA_DIR"] = str(self.tmp) self.config_dir = self.tmp / "config" self.config_dir.mkdir(parents=True, exist_ok=True) def tearDown(self): if self._env is None: os.environ.pop("CMBOT_DATA_DIR", None) else: os.environ["CMBOT_DATA_DIR"] = self._env shutil.rmtree(str(self.tmp), ignore_errors=True) # -- ai_models.json ------------------------------------------------- def test_load_ai_models_missing_returns_empty(self): self.assertEqual(cs.load_ai_models(), []) def test_load_ai_models_object_with_models_list(self): (self.config_dir / "ai_models.json").write_text( json.dumps({"models": [{"name": "m1", "url": "https://x"}]}), encoding="utf-8") models = cs.load_ai_models() self.assertEqual(len(models), 1) self.assertEqual(models[0]["name"], "m1") def test_load_ai_models_bare_list(self): (self.config_dir / "ai_models.json").write_text( json.dumps([{"name": "a"}, {"name": "b"}]), encoding="utf-8") self.assertEqual(len(cs.load_ai_models()), 2) def test_load_ai_models_corrupt_returns_empty(self): (self.config_dir / "ai_models.json").write_text("{ not json", encoding="utf-8") self.assertEqual(cs.load_ai_models(), []) def test_load_ai_models_tolerates_bom(self): (self.config_dir / "ai_models.json").write_bytes( "".encode("utf-8") + json.dumps([{"name": "z"}]).encode("utf-8")) self.assertEqual(cs.load_ai_models()[0]["name"], "z") # -- outfit_prompt.txt ---------------------------------------------- def test_prompt_default_when_missing(self): self.assertEqual(cs.load_outfit_prompt(), cs.DEFAULT_OUTFIT_PROMPT) def test_prompt_save_then_load_roundtrip(self): cs.save_outfit_prompt("hello {title} {product_id}") self.assertEqual(cs.load_outfit_prompt(), "hello {title} {product_id}") def test_prompt_saved_without_bom(self): cs.save_outfit_prompt("abc") raw = (self.config_dir / "outfit_prompt.txt").read_bytes() self.assertFalse(raw.startswith(b"\xef\xbb\xbf")) # -- outfit_prompts.json (multi templates, §7.2) -------------------- def test_prompts_seed_default_when_missing(self): prompts = cs.load_outfit_prompts() self.assertEqual(len(prompts), 1) self.assertEqual(prompts[0]["name"], cs.DEFAULT_OUTFIT_PROMPT_NAME) self.assertEqual(prompts[0]["text"], cs.DEFAULT_OUTFIT_PROMPT) def test_prompts_migrate_legacy_txt(self): (self.config_dir / "outfit_prompt.txt").write_text( "旧话术 {title}", encoding="utf-8") prompts = cs.load_outfit_prompts() self.assertEqual(len(prompts), 1) self.assertEqual(prompts[0]["name"], "默认") self.assertEqual(prompts[0]["text"], "旧话术 {title}") def test_prompts_roundtrip_and_no_bom(self): data = [{"name": "A", "text": "甲 {title}"}, {"name": "B", "text": "乙"}] cs.save_outfit_prompts(data) self.assertEqual(cs.load_outfit_prompts(), data) raw = (self.config_dir / "outfit_prompts.json").read_bytes() self.assertFalse(raw.startswith(b"\xef\xbb\xbf")) def test_prompts_corrupt_falls_back(self): (self.config_dir / "outfit_prompts.json").write_text("{ bad", encoding="utf-8") prompts = cs.load_outfit_prompts() self.assertEqual(len(prompts), 1) self.assertEqual(prompts[0]["name"], cs.DEFAULT_OUTFIT_PROMPT_NAME) def test_prompts_drop_invalid_entries(self): import json (self.config_dir / "outfit_prompts.json").write_text( json.dumps([{"name": "", "text": "x"}, {"name": "ok", "text": "y"}, {"name": "bad", "text": 5}, "nope"]), encoding="utf-8") prompts = cs.load_outfit_prompts() self.assertEqual(prompts, [{"name": "ok", "text": "y"}]) def test_prompts_tolerate_bom(self): import json (self.config_dir / "outfit_prompts.json").write_bytes( "".encode("utf-8") + json.dumps([{"name": "z", "text": "t"}]).encode("utf-8")) self.assertEqual(cs.load_outfit_prompts()[0]["name"], "z") if __name__ == "__main__": unittest.main()