186 lines
6.7 KiB
Python
186 lines
6.7 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""双账号配置加载 / 校验 / 热加载测试(不联网): uv run python tests/test_config.py"""
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
import time
|
|
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.config import ConfigError, ConfigStore, load_config # noqa: E402
|
|
|
|
|
|
def write(path: Path, data) -> None:
|
|
path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
|
|
|
|
def bump_mtime(path: Path) -> None:
|
|
target = max(path.stat().st_mtime_ns, time.time_ns()) + 1_000_000
|
|
os.utime(path, ns=(target, target))
|
|
|
|
|
|
def base_config() -> dict:
|
|
return {
|
|
"bilibili": {
|
|
"read": {"mid": 11, "csrf": "read-csrf", "cookie": "read-cookie"},
|
|
"write": {"mid": 22, "csrf": "write-csrf", "cookie": "write-cookie"},
|
|
},
|
|
"overrides": {"1": {"mid": 2, "name": "x"}},
|
|
"sleep": {"min": 1},
|
|
"folders": [
|
|
{"year": 2025, "media_id": 4451053851, "title": "2025"},
|
|
{"year": 2026, "media_id": 3700023351, "title": "2026"},
|
|
],
|
|
"track_fans": [37754047],
|
|
"up_blacklist_seed": [1, 2, 2],
|
|
}
|
|
|
|
|
|
def test_dual_accounts_and_env_override():
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
path = Path(tmp) / "config.json"
|
|
write(path, base_config())
|
|
env = {
|
|
"BILI_READ_MID": "101", "BILI_READ_CSRF": "env-read", "BILI_READ_COOKIE": "env-read-cookie",
|
|
"BILI_WRITE_MID": "202", "BILI_WRITE_CSRF": "env-write", "BILI_WRITE_COOKIE": "env-write-cookie",
|
|
"DATABASE_PATH": "custom.db",
|
|
}
|
|
cfg = load_config(path, env=env)
|
|
assert (cfg.bilibili.read.mid, cfg.bilibili.read.csrf, cfg.bilibili.read.cookie) == (101, "env-read", "env-read-cookie")
|
|
assert (cfg.bilibili.write.mid, cfg.bilibili.write.csrf, cfg.bilibili.write.cookie) == (202, "env-write", "env-write-cookie")
|
|
assert cfg.storage.database_path == Path(tmp) / "custom.db"
|
|
assert cfg.up_blacklist_seed == (1, 2)
|
|
assert cfg.discover.keywords == ("咻咻满 reaction", "咻咻满 充电")
|
|
assert cfg.discover.pages == 2
|
|
assert cfg.classify["self_mid"] == 37754047
|
|
|
|
|
|
def test_flat_and_missing_account_rejected():
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
path = Path(tmp) / "config.json"
|
|
write(path, {"mid": 1, "csrf": "x", "cookie": "y", "folders": []})
|
|
try:
|
|
load_config(path, env={})
|
|
except ConfigError as exc:
|
|
assert "bilibili.read" in str(exc)
|
|
else:
|
|
raise AssertionError("flat 格式必须拒绝")
|
|
write(path, {"bilibili": {"read": {}}, "folders": []})
|
|
try:
|
|
load_config(path, env={})
|
|
except ConfigError as exc:
|
|
assert "bilibili.write" in str(exc)
|
|
else:
|
|
raise AssertionError("缺少 write 必须拒绝")
|
|
|
|
|
|
def test_validation_require_read_auth_and_hot_reload():
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
path = Path(tmp) / "config.json"
|
|
data = base_config()
|
|
data["bilibili"]["read"].update(cookie="", csrf="")
|
|
write(path, data)
|
|
load_config(path, env={}, require_auth=False)
|
|
try:
|
|
load_config(path, env={}, require_auth=True)
|
|
except ConfigError as exc:
|
|
assert "bilibili.read.cookie" in str(exc)
|
|
else:
|
|
raise AssertionError("read 空凭据必须拒绝")
|
|
|
|
write(path, base_config())
|
|
store = ConfigStore(path, env={})
|
|
data = base_config()
|
|
data["folders"][0]["title"] = "2025-new"
|
|
write(path, data)
|
|
bump_mtime(path)
|
|
assert store.get().folders[0].title == "2025-new"
|
|
path.write_text("{bad", encoding="utf-8")
|
|
bump_mtime(path)
|
|
assert store.get().folders[0].title == "2025-new"
|
|
assert store.last_error
|
|
|
|
|
|
def test_invalid_values():
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
path = Path(tmp) / "config.json"
|
|
data = base_config()
|
|
data["bilibili"]["write"]["mid"] = "bad"
|
|
write(path, data)
|
|
try:
|
|
load_config(path, env={})
|
|
except ConfigError as exc:
|
|
assert "bilibili.write.mid" in str(exc)
|
|
else:
|
|
raise AssertionError("非法 mid 必须拒绝")
|
|
write(path, base_config())
|
|
try:
|
|
load_config(path, env={"BILI_READ_MID": "bad"})
|
|
except ConfigError as exc:
|
|
assert "BILI_READ_MID" in str(exc)
|
|
else:
|
|
raise AssertionError("非法环境变量必须拒绝")
|
|
|
|
|
|
def test_analytics_defaults_and_validation():
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
path = Path(tmp) / "config.json"
|
|
write(path, base_config())
|
|
cfg = load_config(path, env={})
|
|
assert cfg.analytics.min_duration_seconds == 600
|
|
assert cfg.analytics.maturity_days == 2
|
|
assert cfg.analytics.min_view_for_ranking == 2000
|
|
assert cfg.analytics.video_shrink_minutes == 60
|
|
assert cfg.analytics.shrink_minutes is None
|
|
|
|
data = base_config()
|
|
data["analytics"] = {
|
|
"min_duration_seconds": 300,
|
|
"maturity_days": 7,
|
|
"min_view_for_ranking": 100,
|
|
"video_shrink_minutes": 30,
|
|
"shrink_minutes": 90,
|
|
}
|
|
write(path, data)
|
|
cfg = load_config(path, env={})
|
|
assert cfg.analytics.min_duration_seconds == 300
|
|
assert cfg.analytics.maturity_days == 7
|
|
assert cfg.analytics.min_view_for_ranking == 100
|
|
assert cfg.analytics.video_shrink_minutes == 30
|
|
assert cfg.analytics.shrink_minutes == 90
|
|
|
|
for bad, hint in (
|
|
({"min_duration_seconds": -1}, "min_duration_seconds"),
|
|
({"maturity_days": -1}, "maturity_days"),
|
|
({"min_view_for_ranking": -1}, "min_view_for_ranking"),
|
|
({"video_shrink_minutes": -1}, "video_shrink_minutes"),
|
|
({"shrink_minutes": 0}, "shrink_minutes"),
|
|
({"shrink_minutes": "auto"}, "shrink_minutes"),
|
|
):
|
|
data = base_config()
|
|
data["analytics"] = bad
|
|
write(path, data)
|
|
try:
|
|
load_config(path, env={})
|
|
except ConfigError as exc:
|
|
assert hint in str(exc), (bad, str(exc))
|
|
else:
|
|
raise AssertionError(f"非法 analytics 必须拒绝: {bad}")
|
|
|
|
|
|
def main():
|
|
test_dual_accounts_and_env_override()
|
|
test_flat_and_missing_account_rejected()
|
|
test_validation_require_read_auth_and_hot_reload()
|
|
test_invalid_values()
|
|
test_analytics_defaults_and_validation()
|
|
print("OK: 双账号配置加载、拒绝 flat 格式、ENV 覆盖、热加载与 analytics 测试全部通过")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|