返回 last30days-skill
test_codex_host_contract.py
根目录 / tests / test_codex_host_contract.py
1 """Host-contract tests for non-modal agent runtimes."""
2
3 from __future__ import annotations
4
5 from pathlib import Path
6
7 ROOT = Path(__file__).resolve().parents[1]
8 SKILL_MD = ROOT / "skills" / "last30days" / "SKILL.md"
9
10
11 def _prose_flow() -> str:
12 text = SKILL_MD.read_text(encoding="utf-8")
13 start_marker = "### Non-Modal Prose Flow"
14 end_marker = "### Manual Setup Guide"
15 start = text.find(start_marker)
16 assert start != -1, f"missing section marker: {start_marker}"
17 end = text.find(end_marker, start)
18 assert end != -1, f"missing section marker: {end_marker}"
19 return text[start:end]
20
21
22 def test_non_modal_hosts_are_named():
23 prose = _prose_flow()
24 for host in ("Codex", "Cursor", "Gemini CLI", "raw CLI"):
25 assert host in prose
26
27
28 def test_non_modal_cookie_consent_uses_engine_allow_flag():
29 prose = _prose_flow()
30 consent = prose.index("Cookie consent")
31 allow = prose.index("setup --allow-browser-cookies")
32 decline = prose.index("FROM_BROWSER=off")
33 assert consent < allow
34 assert consent < decline
35
36
37 def test_non_modal_preflight_runs_before_cookie_consent():
38 prose = _prose_flow()
39 preflight = prose.index("--preflight")
40 consent = prose.index("Cookie consent")
41 assert preflight < consent
42 assert "does not read browser-cookie values" in prose
43 assert "does not write setup/config/report files" in prose
44 assert "does not run research" in prose
45
46
47 def test_non_modal_completion_mentions_safe_diagnose_and_project_trust():
48 prose = _prose_flow()
49 assert "safe `--diagnose`" in prose
50 assert "LAST30DAYS_TRUST_PROJECT_CONFIG=1" in prose
51 assert "Codex desktop" in prose
52
53
54 def _step0_search_contract() -> str:
55 text = SKILL_MD.read_text(encoding="utf-8")
56 start_marker = "**STEP 0 - RESOLVE HOST WEB SEARCH FIRST.**"
57 end_marker = "**FIRST-RUN GATE"
58 start = text.find(start_marker)
59 assert start != -1, f"missing section marker: {start_marker}"
60 end = text.find(end_marker, start)
61 assert end != -1, f"missing section marker: {end_marker}"
62 return text[start:end]
63
64
65 def test_host_web_search_uses_available_capability_not_specific_tool_name():
66 step0 = _step0_search_contract()
67 assert "usable web-search tool" in step0
68 assert "built in, exposed as a deferred tool, or provided by an installed connector" in step0
69 assert "Brave, Firecrawl, Exa, Serper" in step0
70 assert "If your host requires loading, selecting, or enabling the web-search tool" in step0
71 assert "Do not fail the skill just because one particular schema lookup or tool name is unavailable" in step0
72
73
74 def test_no_host_search_uses_auto_resolve_and_leaves_native_signal_unset():
75 step0 = _step0_search_contract()
76 assert "If no web-search tool is available in the agent session" in step0
77 assert "--auto-resolve" in step0
78 assert "LAST30DAYS_NATIVE_SEARCH=1" in step0
79 assert "Leave it unset when the agent session has no web-search tool" in step0
80
81
82 def _law8_block() -> str:
83 text = SKILL_MD.read_text(encoding="utf-8")
84 start = text.find("**LAW 8 -")
85 assert start != -1, "missing LAW 8 marker"
86 end = text.find("**LAW 9 -", start)
87 assert end != -1, "missing LAW 9 marker (LAW 8 block end)"
88 return text[start:end]
89
90
91 def test_law8_is_renderer_aware_with_both_regimes():
92 # LAW 8 must keep the inline-link default for hidden-link hosts AND carry a
93 # plain-label branch for visible-URL hosts. Codex rendered every inline link
94 # as `label (https://...)`, so a single-renderer LAW 8 produced URL soup.
95 law8 = _law8_block()
96 assert "Hidden-link hosts (Claude Code)" in law8
97 assert "Visible-URL hosts (Codex" in law8
98 assert "URL soup" in law8
99 # Hidden-link default must remain inline `[name](url)` (no Claude Code regression).
100 assert "`[name](url)`" in law8
101
102
103 def test_law8_host_detection_is_deterministic_via_claudecode():
104 law8 = _law8_block()
105 assert "CLAUDECODE" in law8
106 # The detection must be stated as deterministic, not left to the model guessing.
107 assert "do not guess" in law8
108
109
110 def test_plan_invocation_warns_against_bash_lc_apostrophe_wrapper():
111 # Codex aborted its first engine run by wrapping the query-plan heredoc in
112 # `bash -lc '...'`; the outer single quote ended at the first apostrophe in a
113 # ranking string. The guidance must steer off that wrapper explicitly.
114 text = SKILL_MD.read_text(encoding="utf-8")
115 assert "bash -lc '...'" in text
116 assert "unmatched" in text
117
118
119 def test_step055_documents_dedicated_vs_broad_subreddits():
120 # Step 0.55 must instruct the model to split entity-home (dedicated) subs from
121 # broad subs and pass them via --dedicated-subreddits, which the engine pulls
122 # in full and exempts from the relevance floor.
123 text = SKILL_MD.read_text(encoding="utf-8")
124 assert "RESOLVED_DEDICATED_SUBREDDITS" in text
125 assert "--dedicated-subreddits" in text
126 assert "relevance floor" in text
127
127 lines PYTHON