返回 last30days-skill
test_env_doc_contract.py
根目录 / tests / test_env_doc_contract.py
1 from __future__ import annotations
2
3 import re
4 from pathlib import Path
5 from unittest import mock
6
7 from lib import env
8
9
10 ROOT = Path(__file__).resolve().parents[1]
11 DOC_PATHS = [
12 ROOT / "skills" / "last30days" / "SKILL.md",
13 ROOT / "README.md",
14 ROOT / "CONFIGURATION.md",
15 ]
16 CONFIG_ENV_KEY_RE = re.compile(
17 r"(?<![A-Z0-9_])(?:"
18 r"LAST30DAYS_[A-Z0-9_]+|"
19 r"(?:XAI|GOOGLE|GEMINI|GOOGLE_GENAI|XIAOHONGSHU|SCRAPECREATORS|APIFY|"
20 r"BSKY|TRUTHSOCIAL|BRAVE|EXA|SERPER|OPENROUTER|PERPLEXITY|PARALLEL|"
21 r"XQUIK|GROQ)_[A-Z0-9_]+|"
22 r"OPENAI_API_KEY|AUTH_TOKEN|CT0|FROM_BROWSER|INCLUDE_SOURCES|"
23 r"EXCLUDE_SOURCES|SETUP_COMPLETE|FUN_LEVEL|X_BEARER_TOKEN"
24 r")(?![A-Z0-9_])"
25 )
26 DOC_ONLY_KEYS = {
27 "LAST30DAYS_API_BASE",
28 "LAST30DAYS_API_KEY",
29 "LAST30DAYS_CACHE_DIR",
30 "LAST30DAYS_MCP_TIMEOUT",
31 "LAST30DAYS_PYTHON",
32 # Read from the process environment before or outside get_config
33 # (config-dir override, Keychain/pass source switches), so they are
34 # documented in CONFIGURATION.md without being config keys.
35 "LAST30DAYS_CONFIG_DIR",
36 "LAST30DAYS_PASS_PREFIX",
37 "LAST30DAYS_SKIP_KEYCHAIN",
38 }
39
40
41 def _neutral_secret_sources():
42 return (
43 mock.patch.object(env, "_load_keychain", return_value={}),
44 mock.patch.object(env, "_load_pass", return_value={}),
45 )
46
47
48 def _documented_env_keys() -> set[str]:
49 keys: set[str] = set()
50 for path in DOC_PATHS:
51 keys.update(CONFIG_ENV_KEY_RE.findall(path.read_text(encoding="utf-8")))
52 return keys
53
54
55 def test_registered_user_config_keys_round_trip_from_env_file(tmp_path, monkeypatch):
56 config_file = tmp_path / ".env"
57 config_file.write_text(
58 "FUN_LEVEL=high\nLAST30DAYS_REPORT_CACHE_TTL_SECONDS=120\n",
59 encoding="utf-8",
60 )
61 config_file.chmod(0o600)
62 monkeypatch.setenv("LAST30DAYS_CONFIG_DIR", str(tmp_path))
63 monkeypatch.delenv("FUN_LEVEL", raising=False)
64 monkeypatch.delenv("LAST30DAYS_REPORT_CACHE_TTL_SECONDS", raising=False)
65 monkeypatch.setattr(env, "CONFIG_DIR", tmp_path)
66 monkeypatch.setattr(env, "CONFIG_FILE", config_file)
67 monkeypatch.chdir(tmp_path)
68
69 keychain, pass_store = _neutral_secret_sources()
70 with keychain, pass_store:
71 config = env.get_config()
72
73 assert config["FUN_LEVEL"] == "high"
74 assert config["LAST30DAYS_REPORT_CACHE_TTL_SECONDS"] == "120"
75
76
77 def test_documented_env_keys_are_registered_in_get_config(tmp_path, monkeypatch):
78 monkeypatch.setenv("LAST30DAYS_CONFIG_DIR", str(tmp_path))
79 monkeypatch.setattr(env, "CONFIG_DIR", tmp_path)
80 monkeypatch.setattr(env, "CONFIG_FILE", tmp_path / "does-not-exist.env")
81 monkeypatch.chdir(tmp_path)
82
83 keychain, pass_store = _neutral_secret_sources()
84 with keychain, pass_store:
85 registered_keys = set(env.get_config())
86
87 missing = sorted(_documented_env_keys() - DOC_ONLY_KEYS - registered_keys)
88
89 assert missing == []
90
90 lines PYTHON