# -*- coding: utf-8 -*- """config.json → jsonc + secrets 拆分脚本测试(离线): uv run python tests/test_split_config_secrets.py""" from __future__ import annotations import json import os import sys import tempfile from pathlib import Path sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.stdout.reconfigure(encoding="utf-8") from reaction_board import jsonc # noqa: E402 from reaction_board.config import load_config # noqa: E402 from tools.split_config_secrets import ( # noqa: E402 split_config_file, split_raw, ) def _monolith() -> dict: return { "bilibili": { "read": {"mid": 11, "csrf": "read-csrf", "cookie": "SESSDATA=secret-read"}, "write": {"mid": 22, "csrf": "write-csrf", "cookie": "SESSDATA=secret-write"}, }, "telegram": { "enabled": True, "bot_token": "tg-secret-token", "chat_id": 12345, }, "web": { "host": "127.0.0.1", "port": 8080, "auth_token": "web-secret-token", "public_base_url": "", }, "folders": [{"year": 2026, "media_id": 1, "title": "t"}], "track_fans": [37754047], "up_blacklist_seed": [], } def test_split_raw_extracts_secrets_keeps_business(): business, secrets, stats = split_raw(_monolith()) assert "bilibili" not in business assert business["telegram"] == {"enabled": True} assert business["web"]["host"] == "127.0.0.1" assert business["web"]["port"] == 8080 assert "auth_token" not in business["web"] assert secrets["bilibili"]["read"]["mid"] == 11 assert secrets["telegram"] == {"bot_token": "tg-secret-token", "chat_id": 12345} assert secrets["web"] == {"auth_token": "web-secret-token"} assert "bilibili" in stats["moved_paths"] assert "telegram.bot_token" in stats["moved_paths"] assert "web.auth_token" in stats["moved_paths"] def test_split_raw_omits_absent_secret_stubs(): raw = { "telegram": {"enabled": False}, "web": {"host": "0.0.0.0", "port": 9}, "folders": [], } business, secrets, stats = split_raw(raw) assert secrets == {} assert business["telegram"] == {"enabled": False} assert business["web"]["host"] == "0.0.0.0" assert stats["moved_count"] == 0 def test_dry_run_writes_nothing(): with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) src = root / "config.json" src.write_text(json.dumps(_monolith(), ensure_ascii=False), encoding="utf-8") summary = split_config_file(src, dry_run=True) assert summary["dry_run"] is True assert summary["moved_count"] >= 3 assert not (root / "config.jsonc").exists() assert not (root / "secrets.json").exists() assert src.exists() # 摘要不得泄露 cookie/token blob = json.dumps(summary, ensure_ascii=False) assert "SESSDATA" not in blob assert "tg-secret-token" not in blob assert "web-secret-token" not in blob def test_refuses_overwrite_without_force(): with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) src = root / "config.json" src.write_text(json.dumps(_monolith(), ensure_ascii=False), encoding="utf-8") (root / "config.jsonc").write_text("// existing\n{}\n", encoding="utf-8") try: split_config_file(src, keep_src=True) except FileExistsError as exc: assert "config.jsonc" in str(exc) assert "--force" in str(exc) else: raise AssertionError("已有输出时无 --force 必须失败") assert src.exists() assert "existing" in (root / "config.jsonc").read_text(encoding="utf-8") def test_writes_and_renames_src_by_default(): with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) src = root / "config.json" src.write_text(json.dumps(_monolith(), ensure_ascii=False), encoding="utf-8") summary = split_config_file(src) cfg_path = root / "config.jsonc" sec_path = root / "secrets.json" bak = root / "config.json.bak" assert summary["ok"] is True assert not src.exists() assert bak.exists() assert cfg_path.exists() assert sec_path.exists() text = cfg_path.read_text(encoding="utf-8") assert text.startswith("//") assert "secrets.json" in text business = jsonc.load(cfg_path) secrets = json.loads(sec_path.read_text(encoding="utf-8")) assert "bilibili" not in business assert business["telegram"]["enabled"] is True assert "bot_token" not in business["telegram"] assert business["web"]["host"] == "127.0.0.1" assert "auth_token" not in business["web"] assert secrets["bilibili"]["read"]["mid"] == 11 assert secrets["telegram"]["chat_id"] == 12345 assert secrets["web"]["auth_token"] == "web-secret-token" loaded = load_config(cfg_path, env={}, require_auth=False) assert loaded.bilibili.read.mid == 11 assert loaded.bilibili.write.mid == 22 assert loaded.telegram.enabled is True assert loaded.telegram.bot_token == "tg-secret-token" assert loaded.web.host == "127.0.0.1" assert loaded.web.auth_token == "web-secret-token" assert summary["verify"]["checked"] is True assert summary["verify"]["read_mid"] == 11 def test_force_overwrite_and_keep_src(): with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) src = root / "config.json" src.write_text(json.dumps(_monolith(), ensure_ascii=False), encoding="utf-8") (root / "config.jsonc").write_text("{}\n", encoding="utf-8") (root / "secrets.json").write_text("{}\n", encoding="utf-8") summary = split_config_file(src, force=True, keep_src=True) assert src.exists() assert summary["src_renamed"] is False secrets = json.loads((root / "secrets.json").read_text(encoding="utf-8")) assert secrets["bilibili"]["read"]["mid"] == 11 def test_honors_secrets_path_relative(): with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) src = root / "config.json" data = _monolith() data["secrets_path"] = "creds/my-secrets.json" src.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8") summary = split_config_file(src, keep_src=True) sec = root / "creds" / "my-secrets.json" assert Path(summary["secrets_out"]) == sec assert sec.exists() business = jsonc.load(root / "config.jsonc") assert business.get("secrets_path") == "creds/my-secrets.json" def main(): test_split_raw_extracts_secrets_keeps_business() test_split_raw_omits_absent_secret_stubs() test_dry_run_writes_nothing() test_refuses_overwrite_without_force() test_writes_and_renames_src_by_default() test_force_overwrite_and_keep_src() test_honors_secrets_path_relative() print( "OK: split_config_secrets 抽取 bilibili/telegram/web 凭据、" "dry-run、拒绝覆盖、改名与 secrets_path 测试全部通过" ) if __name__ == "__main__": main()