返回 last30days-skill
test_x_extras_gating.py
根目录 / tests / test_x_extras_gating.py
1 """Host-gated X cookie extras: MacBook stays on main; Linux/Mac mini/sink get
2 the two extra bird cookie lookups (agentcookie sidecar, live Chrome CDP).
3
4 Locks the acceptance examples:
5 * AE7 Linux, no bird cookies, grok AUTH_OK + XAI_API_KEY -> xai (not grok).
6 * AE8 MacBook (hw.model MacBookPro), FROM_BROWSER unset, agentcookie on PATH,
7 grok AUTH_OK + XAI_API_KEY -> xai; NO agentcookie subprocess, NO CDP.
8 * AE10 Darwin Mac mini (hw.model Macmini9,1), agentcookie sidecar pair,
9 FROM_BROWSER unset -> bird from agentcookie. ~/.hermes never flips a
10 MacBook into extras.
11
12 Only obvious dummy cookie values are used (test-auth-token / test-ct0).
13 """
14
15 from unittest import mock
16
17 from lib import env
18
19 _PAIR = {"auth_token": "test-auth-token", "ct0": "test-ct0"}
20
21
22 def _no_extract():
23 """Patch the mainline browser extractor to a no-op (FROM_BROWSER unset)."""
24 return mock.patch.object(env, "extract_browser_credentials", return_value={})
25
26
27 def _stub_backends():
28 """Local-only backend probes so get_x_source touches no network.
29
30 Returns an ExitStack already entered; use as ``with _stub_backends():``.
31 """
32 import contextlib
33 stack = contextlib.ExitStack()
34 stack.enter_context(mock.patch("lib.bird_x.is_bird_installed", return_value=True))
35 stack.enter_context(mock.patch("lib.bird_x.set_credentials", lambda *a, **k: None))
36 stack.enter_context(mock.patch("lib.xurl_x.is_available", return_value=False))
37 return stack
38
39
40 # --- x_extras_enabled matrix ----------------------------------------------
41
42
43 def test_extras_enabled_on_linux():
44 with mock.patch("platform.system", return_value="Linux"):
45 assert env.x_extras_enabled({}) is True
46
47
48 def test_extras_enabled_on_mac_mini():
49 with (
50 mock.patch("platform.system", return_value="Darwin"),
51 mock.patch.object(env, "_mac_model", return_value="Macmini9,1"),
52 ):
53 assert env.x_extras_enabled({}) is True
54
55
56 def test_extras_enabled_on_darwin_sink_role():
57 with (
58 mock.patch("platform.system", return_value="Darwin"),
59 mock.patch.object(env, "_mac_model", return_value="MacBookPro18,2"),
60 mock.patch("lib.agentcookie.role_is_sink", return_value=True),
61 ):
62 assert env.x_extras_enabled({}) is True
63
64
65 def test_extras_disabled_on_plain_macbook():
66 with (
67 mock.patch("platform.system", return_value="Darwin"),
68 mock.patch.object(env, "_mac_model", return_value="MacBookPro18,2"),
69 mock.patch("lib.agentcookie.role_is_sink", return_value=False),
70 ):
71 assert env.x_extras_enabled({}) is False
72
73
74 def test_agentcookie_on_opts_in_a_macbook():
75 with (
76 mock.patch("platform.system", return_value="Darwin"),
77 mock.patch.object(env, "_mac_model", return_value="MacBookPro18,2"),
78 ):
79 assert env.x_extras_enabled({"AGENTCOOKIE": "on"}) is True
80
81
82 def test_agentcookie_off_keeps_macbook_off():
83 with (
84 mock.patch("platform.system", return_value="Darwin"),
85 mock.patch.object(env, "_mac_model", return_value="MacBookPro18,2"),
86 mock.patch("lib.agentcookie.role_is_sink", return_value=False),
87 ):
88 assert env.x_extras_enabled({"AGENTCOOKIE": "off"}) is False
89
90
91 def test_windows_has_no_extras():
92 with mock.patch("platform.system", return_value="Windows"):
93 assert env.x_extras_enabled({}) is False
94
95
96 def test_hermes_home_and_env_never_flip_extras():
97 """~/.hermes / HERMES_AGENT / OPENCLAW_CLI must NOT enable extras (AE10)."""
98 with (
99 mock.patch("platform.system", return_value="Darwin"),
100 mock.patch.object(env, "_mac_model", return_value="MacBookPro18,2"),
101 mock.patch("lib.agentcookie.role_is_sink", return_value=False),
102 mock.patch.dict("os.environ", {"HERMES_AGENT": "1", "OPENCLAW_CLI": "1"}, clear=False),
103 ):
104 assert env.x_extras_enabled({}) is False
105
106
107 # --- AE8: MacBook is untouched --------------------------------------------
108
109
110 def test_ae8_macbook_no_extras_no_subprocess_and_picks_xai():
111 config = {"XAI_API_KEY": "dummy-xai-key"} # no AUTH_TOKEN/CT0, FROM_BROWSER unset
112 with (
113 mock.patch("platform.system", return_value="Darwin"),
114 mock.patch.object(env, "_mac_model", return_value="MacBookPro18,2"),
115 mock.patch("lib.agentcookie.role_is_sink", return_value=False),
116 # These MUST NOT run on a MacBook:
117 mock.patch("lib.agentcookie.read_x_cookies", side_effect=AssertionError("no agentcookie subprocess on MacBook")),
118 mock.patch("lib.chrome_cdp.read_x_cookies", side_effect=AssertionError("no CDP on MacBook")),
119 _no_extract(),
120 ):
121 env._discover_and_apply_x_credentials(config)
122 assert config.get("AUTH_TOKEN") is None
123 assert config.get("CT0") is None
124 # Backend selection: leftover grok + XAI_API_KEY must use xai (grok pin-only).
125 with mock.patch("lib.grok_x.has_stored_auth", return_value=True), _stub_backends():
126 assert env.get_x_source(config) == "xai"
127
128
129 # --- AE10: Mac mini gets the sidecar pair ---------------------------------
130
131
132 def test_ae10_mac_mini_reads_bird_pair_from_agentcookie():
133 config = {} # FROM_BROWSER unset
134 with (
135 mock.patch("platform.system", return_value="Darwin"),
136 mock.patch.object(env, "_mac_model", return_value="Macmini9,1"),
137 mock.patch("lib.agentcookie.read_x_cookies", return_value=dict(_PAIR)),
138 mock.patch("lib.chrome_cdp.read_x_cookies", side_effect=AssertionError("agentcookie already won")),
139 _no_extract(),
140 ):
141 env._discover_and_apply_x_credentials(config)
142 assert config["AUTH_TOKEN"] == "test-auth-token"
143 assert config["CT0"] == "test-ct0"
144 assert config["_AUTH_TOKEN_SOURCE"] == "agentcookie"
145 with mock.patch("lib.grok_x.has_stored_auth", return_value=False), _stub_backends():
146 assert env.get_x_source(config) == "bird"
147
148
149 # --- AE7: Linux picks xai over a stale grok --------------------------------
150
151
152 def test_ae7_linux_no_cookies_grok_and_xai_picks_xai():
153 config = {"XAI_API_KEY": "dummy-xai-key"}
154 with (
155 mock.patch("platform.system", return_value="Linux"),
156 mock.patch("lib.agentcookie.read_x_cookies", return_value=None),
157 mock.patch("lib.chrome_cdp.read_x_cookies", return_value=None),
158 _no_extract(),
159 ):
160 env._discover_and_apply_x_credentials(config)
161 assert config.get("AUTH_TOKEN") is None
162 assert config.get("CT0") is None
163 with mock.patch("lib.grok_x.has_stored_auth", return_value=True), _stub_backends():
164 assert env.get_x_source(config) == "xai"
165
166
167 # --- discovery ordering / no-overwrite ------------------------------------
168
169
170 def test_explicit_env_pair_not_overwritten_on_extra_host():
171 config = {"AUTH_TOKEN": "explicit-token", "CT0": "explicit-ct0"}
172 with (
173 mock.patch("platform.system", return_value="Linux"),
174 mock.patch("lib.agentcookie.read_x_cookies", side_effect=AssertionError("no discovery with complete env pair")),
175 mock.patch("lib.chrome_cdp.read_x_cookies", side_effect=AssertionError("no discovery with complete env pair")),
176 _no_extract(),
177 ):
178 env._discover_and_apply_x_credentials(config)
179 assert config["AUTH_TOKEN"] == "explicit-token"
180 assert config["CT0"] == "explicit-ct0"
181
182
183 def test_cdp_used_when_agentcookie_empty_on_extra_host():
184 config = {}
185 with (
186 mock.patch("platform.system", return_value="Linux"),
187 mock.patch("lib.agentcookie.read_x_cookies", return_value=None),
188 mock.patch("lib.chrome_cdp.read_x_cookies", return_value=dict(_PAIR)),
189 _no_extract(),
190 ):
191 env._discover_and_apply_x_credentials(config)
192 assert config["AUTH_TOKEN"] == "test-auth-token"
193 assert config["_AUTH_TOKEN_SOURCE"] == "chrome cdp"
194
195
196 # --- AE1: Grok Bot host, cookies present, no pin ---------------------------
197
198
199 def test_ae1_grok_bot_blocks_scraper_xquik_and_every_cookie_leg():
200 """LAST30DAYS_HOST=grok-bot on Linux with every cookie source armed: the
201 chain has neither bird nor xquik, no sidecar / CDP / browser-store read
202 is made for any cookie domain, and the scraper is never primed."""
203 config = {
204 "LAST30DAYS_HOST": "grok-bot",
205 "FROM_BROWSER": "firefox",
206 "AGENTCOOKIE": "on",
207 "BROWSER_CDP_URL": "http://127.0.0.1:18800",
208 "AUTH_TOKEN": "test-auth-token",
209 "CT0": "test-ct0",
210 "XQUIK_API_KEY": "dummy-xquik-key",
211 }
212 with (
213 mock.patch("platform.system", return_value="Linux"),
214 mock.patch("shutil.which", return_value="/usr/local/bin/agentcookie"),
215 mock.patch("lib.agentcookie.read_x_cookies", side_effect=AssertionError("no sidecar on grok-bot")),
216 mock.patch("lib.chrome_cdp.read_x_cookies", side_effect=AssertionError("no CDP on grok-bot")),
217 mock.patch("lib.cookie_extract.extract_cookies", side_effect=AssertionError("no browser store read on grok-bot")),
218 mock.patch("lib.bird_x.set_credentials", side_effect=AssertionError("scraper must not be primed on grok-bot")),
219 mock.patch("lib.bird_x.is_bird_installed", return_value=True),
220 mock.patch("lib.xurl_x.is_available", return_value=False),
221 mock.patch("lib.xurl_x.has_stored_auth", return_value=False),
222 ):
223 env._discover_and_apply_x_credentials(config)
224 assert env.cookie_extraction_browsers(config) == []
225 chain = env.x_backend_chain(config)
226 assert "bird" not in chain
227 assert "xquik" not in chain
228 assert chain == []
229 assert env.get_x_source(config) is None
230 # Preflight reports browser cookies off: no browser resolves.
231 config["_BROWSER_COOKIE_MODE"] = "plan_only"
232 assert env.x_pending_browser_auth(config) is False
233 # X becomes available only through an official path.
234 assert env.x_backend_chain({**config, "X_BEARER_TOKEN": "dummy-bearer"}) == ["xapi"]
235 assert env.x_backend_chain({**config, "XAI_API_KEY": "dummy-xai-key"}) == ["xai"]
236
237
238 def test_ae1_get_config_read_mode_on_grok_bot_reports_no_browsers(tmp_path, monkeypatch):
239 """The resolved config's _BROWSER_COOKIE_BROWSERS is empty on a Grok Bot
240 host even with FROM_BROWSER set, and read mode performs no cookie leg."""
241 monkeypatch.setenv("LAST30DAYS_CONFIG_DIR", str(tmp_path))
242 monkeypatch.setattr(env, "CONFIG_DIR", tmp_path)
243 monkeypatch.setattr(env, "CONFIG_FILE", tmp_path / "does-not-exist.env")
244 monkeypatch.chdir(tmp_path)
245 monkeypatch.setenv("LAST30DAYS_HOST", "grok-bot")
246 monkeypatch.setenv("FROM_BROWSER", "firefox")
247 monkeypatch.setenv("AGENTCOOKIE", "on")
248 monkeypatch.delenv("LAST30DAYS_X_BACKEND", raising=False)
249 with (
250 mock.patch.object(env, "_load_keychain", return_value={}),
251 mock.patch.object(env, "_load_pass", return_value={}),
252 mock.patch.object(env, "_find_project_env", return_value=None),
253 mock.patch("platform.system", return_value="Linux"),
254 mock.patch("lib.agentcookie.read_x_cookies", side_effect=AssertionError("no sidecar on grok-bot")),
255 mock.patch("lib.chrome_cdp.read_x_cookies", side_effect=AssertionError("no CDP on grok-bot")),
256 mock.patch("lib.cookie_extract.extract_cookies", side_effect=AssertionError("no browser store read on grok-bot")),
257 ):
258 config = env.get_config(env.ConfigLoadPolicy(browser_cookies="read"))
259 assert config["_BROWSER_COOKIE_BROWSERS"] == []
260 assert config["_BROWSER_COOKIE_MODE"] == "read"
261 assert config.get("_AUTH_TOKEN_SOURCE") is None
262
263
264 # --- AE7a: ambient bearer on a non-Grok host never reaches xapi ------------
265
266
267 def test_ae7a_non_grok_host_ambient_bearer_never_reaches_xapi():
268 config = {
269 "AUTH_TOKEN": "test-auth-token",
270 "CT0": "test-ct0",
271 "X_BEARER_TOKEN": "dummy-bearer",
272 }
273 with (
274 mock.patch("platform.system", return_value="Linux"),
275 mock.patch("lib.http.get", side_effect=AssertionError("no X API request off Grok Bot")),
276 _stub_backends(),
277 ):
278 chain = env.x_backend_chain(config)
279 # The X loop fails over only along this chain: bird returning nothing
280 # leaves no next rung, so no X API request is possible.
281 assert chain == ["bird"]
282 assert "xapi" not in chain
283 with _stub_backends():
284 assert env.x_backend_chain({**config, "LAST30DAYS_X_BACKEND": "xapi"}) == ["xapi"]
285
286
287 # --- AE8: Cursor agent chat without the host key is unchanged --------------
288
289
290 def test_ae8_cursor_agent_without_host_key_is_unchanged():
291 config = {"AGENTCOOKIE": "on"}
292 with (
293 mock.patch.dict("os.environ", {"CURSOR_AGENT": "1"}, clear=False),
294 mock.patch("platform.system", return_value="Linux"),
295 mock.patch("lib.agentcookie.read_x_cookies", return_value=dict(_PAIR)) as sidecar,
296 mock.patch("lib.chrome_cdp.read_x_cookies", return_value=None),
297 _no_extract(),
298 ):
299 env._discover_and_apply_x_credentials(config)
300 assert sidecar.called
301 assert config["_AUTH_TOKEN_SOURCE"] == "agentcookie"
302 with _stub_backends():
303 assert env.x_backend_chain(config) == ["bird"]
304
304 lines PYTHON