返回 last30days-skill
test_grok_surfacing.py
根目录 / tests / test_grok_surfacing.py
1 """grok must be visible as an opt-in backup, not as a default.
2
3 Grok is demoted to opt-in only: a leftover ~/.grok/auth.json must never steal
4 the X lane. The default auto chain is bird → xai → xurl → xquik. Pin
5 LAST30DAYS_X_BACKEND=grok to enable grok explicitly.
6 """
7
8 import inspect
9 from pathlib import Path
10 from unittest import mock
11
12 from lib import backends, doctor, health, quality_nudge
13
14 REPO = Path(__file__).resolve().parent.parent
15
16
17 def test_doctor_does_not_auto_select_grok_unpinned():
18 """Doctor must NOT report 'will use: grok' for unpinned runs.
19 Grok is opt-in only; a leftover auth.json must never steal the X lane."""
20 src = inspect.getsource(doctor._x_record)
21 # The old code would check grok_x.has_stored_auth() and set status="ok"
22 # with record["will_use"]="grok" when grok was available unpinned. That
23 # promotion block is removed: no "has_stored_auth()" call that sets
24 # will_use to grok for unpinned runs.
25 #
26 # Comments may mention "will use: grok" to explain what we DON'T do, so
27 # check for the old logic pattern: has_stored_auth -> grok promotion.
28 assert "has_stored_auth" not in src
29
30
31 def test_doctor_mentions_grok_as_opt_in():
32 """Doctor comments explain that grok is opt-in only."""
33 src = inspect.getsource(doctor._x_record)
34 assert "opt-in" in src.lower()
35
36
37 def test_quality_nudge_does_not_turn_optional_x_into_a_grok_prompt():
38 src = inspect.getsource(quality_nudge)
39 assert "grok_cli_missing" not in src
40 # Unconfigured/declined X is an optional omission, never a setup nudge...
41 assert 'optional_omitted.append("x")' in src
42 assert '"cookies_missing"' not in src
43 # ...but a configured X that errored still surfaces its repair.
44 assert '"cookies_expired"' in src
45
46
47 def test_configuration_documents_the_grok_path_as_opt_in():
48 text = (REPO / "CONFIGURATION.md").read_text()
49 assert "Grok CLI (opt-in backup)" in text
50 assert "grok login" in text
51 # Document that grok requires a pin.
52 assert "LAST30DAYS_X_BACKEND=grok" in text
53
54
55 def test_configuration_pin_row_lists_opt_ins_last():
56 """Pin row shows all backends with the opt-ins (grok, xapi) last."""
57 text = (REPO / "CONFIGURATION.md").read_text()
58 # Auto-chain order first, then the opt-in backends.
59 assert "`bird` / `xai` / `xurl` / `xquik` / `grok` / `xapi`" in text
60
61
62 def test_configuration_does_not_claim_grok_is_free():
63 text = (REPO / "CONFIGURATION.md").read_text()
64 section = text[text.index("Grok CLI (opt-in backup)"):][:1200]
65 assert "draws on your Grok plan" in section or "draw on your Grok plan" in section
66
67
68 def test_configuration_documents_bird_first_chain():
69 """Auto chain is bird first: cookies beat XAI_API_KEY."""
70 text = (REPO / "CONFIGURATION.md").read_text()
71 assert "bird first" in text.lower() or "bird (browser cookies) → xai" in text.lower()
72
73
74 def test_configuration_chain_paragraph_keeps_bird_first_and_adds_grok_bot_exception():
75 """Ordinary hosts keep bird first; the Grok Bot exception is the official chain."""
76 text = (REPO / "CONFIGURATION.md").read_text()
77 start = text.index("**X backend priority (bird first).**")
78 paragraph = text[start:text.index("\n", start)]
79 assert "bird (browser cookies) → xai (API key) → xurl (OAuth2 CLI) → xquik (API key)" in paragraph
80 assert "Grok Bot" in paragraph
81 assert "xapi" in paragraph and "xai" in paragraph and "xurl" in paragraph
82 assert "X connector" in paragraph
83 # Grok CLI stays an opt-in backup on every host.
84 assert "leftover grok login never steals the X lane" in paragraph
85
86
87 def test_changelog_fragments_exist_and_changelog_is_untouched():
88 frags = list((REPO / "changelog.d").glob("*grok*")) + \
89 list((REPO / "changelog.d").glob("*bird*"))
90 if not frags:
91 # Release PRs consume fragments into CHANGELOG.md via towncrier.
92 changelog = (REPO / "CHANGELOG.md").read_text()
93 # Old text or new text about X backend changes.
94 assert "X search now works with no X credential" in changelog or "bird first" in changelog.lower()
95 return
96 assert frags, "feature PRs add a changelog.d fragment"
97
98
99 def test_official_x_api_fragments_exist_and_changelog_is_untouched():
100 """U8: two orphan fragments carry the change; CHANGELOG.md is release-owned."""
101 changed = REPO / "changelog.d" / "+grok-bot-official-x-api.changed.md"
102 added = REPO / "changelog.d" / "+x-api-backend-and-host-lane.added.md"
103 changelog = (REPO / "CHANGELOG.md").read_text()
104 if not changed.exists() and not added.exists():
105 # A release consumed the fragments into CHANGELOG.md via towncrier.
106 assert "X connector" in changelog and "xapi" in changelog
107 return
108 assert changed.exists() and added.exists(), "both U8 fragments must ship together"
109 changed_text = changed.read_text().strip()
110 added_text = added.read_text().strip()
111 assert changed_text and added_text
112 # Fragment bodies are not pasted into CHANGELOG.md by hand.
113 assert changed_text.splitlines()[0] not in changelog
114 assert added_text.splitlines()[0] not in changelog
115 for text in (changed_text, added_text):
116 lowered = text.lower()
117 # R20/R21: the fragments never name the legacy backends, cookies,
118 # a pin as an override path, or why the feature exists.
119 for banned in ("bird", "xquik", "grok cli", "cookie", "override", "xai's request", "marketplace"):
120 assert banned not in lowered, f"fragment names {banned!r}"
121
122
123 # --- SKILL.md unlock surfaces ---------------------------------------------
124
125 def _skill_md():
126 return (REPO / "skills" / "last30days" / "SKILL.md").read_text()
127
128
129 def test_skill_md_does_not_check_grok_first():
130 """Grok is opt-in only: SKILL.md should NOT check for grok before cookies."""
131 text = _skill_md()
132 # The old "Check for a Grok path before asking for cookies" should be removed.
133 assert "Check for a Grok path before asking for cookies" not in text
134
135
136 def test_skill_md_presents_grok_as_opt_in_backup():
137 """SKILL.md presents grok as an opt-in backup, not a primary option."""
138 text = _skill_md()
139 assert "Grok CLI is an opt-in backup" in text
140 # Should mention the pin requirement.
141 assert "LAST30DAYS_X_BACKEND=grok" in text
142
143
144 def test_skill_md_replaces_just_in_time_unlock_with_optional_omission():
145 """A useful report ends without a second X consent or key prompt."""
146 text = _skill_md()
147 assert "Just-in-time X unlock" not in text
148 section = text[text.index("Optional X omission"):][:1200]
149 assert "finish the useful findings first" in section
150 assert "Do not open a modal" in section
151
152
153 def test_skill_md_keeps_grok_paid_caveat_in_explicit_setup_path():
154 text = _skill_md()
155 section = text[text.index("Grok CLI is an opt-in backup"):][:1200]
156 assert "Do not call it free" in section
157
158
159 # --- Doctor grok-only unpinned behavior (R3/R8) -----------------------------
160
161
162 def test_doctor_grok_only_unpinned_is_not_tier_error():
163 """Unpinned grok-only is unconfigured (tier off), NOT broken (tier error).
164
165 R3: unpinned grok is 'available, unused — pin LAST30DAYS_X_BACKEND=grok'
166 R8: grok-only unpinned = X unconfigured / skipped, not auth-failed or broken
167 """
168 from lib import grok_x
169
170 config = {} # No pin, no auto-chain credentials
171 bird_status = {
172 "installed": False,
173 "authenticated": False,
174 "username": "",
175 "can_install": False,
176 }
177 # Grok is the only backend with OK status; all auto-chain backends are MISSING.
178 with (
179 mock.patch.object(grok_x, "binary_path", return_value="/usr/bin/grok"),
180 mock.patch.object(grok_x, "has_stored_auth", return_value=True),
181 mock.patch.object(grok_x, "stored_auth_status", return_value=(grok_x.AUTH_OK, "", None)),
182 mock.patch("lib.backends.which", return_value="/usr/bin/grok"),
183 mock.patch("lib.bird_x.get_bird_status", return_value=bird_status),
184 mock.patch("lib.bird_x.is_bird_installed", return_value=False),
185 mock.patch("lib.xurl_x.has_stored_auth", return_value=False),
186 ):
187 record = doctor._x_record(config)
188 # Must NOT be tier error / NOT WORKING.
189 assert record["tier"] != "error", "grok-only unpinned must not be tier error"
190 # Should be unconfigured (tier off).
191 assert record["status"] == "unconfigured"
192 assert record["tier"] == "off"
193 # Note should mention grok is available but requires a pin.
194 assert "grok" in record["note"].lower()
195 assert "pin" in record["note"].lower() or "LAST30DAYS_X_BACKEND" in record["note"]
196
197
198 def test_doctor_grok_error_unpinned_is_not_tier_error():
199 """Unpinned grok ERROR (broken store) is unconfigured, NOT tier error.
200
201 When grok's auth store is unreadable/corrupt, doctor should NOT report X
202 as NOT WORKING with a `grok login` prescription. An unused opt-in backend
203 with a broken store is still unused — tier off, not tier error.
204 """
205 from lib import grok_x
206
207 config = {} # No pin, no auto-chain credentials
208 bird_status = {
209 "installed": False,
210 "authenticated": False,
211 "username": "",
212 "can_install": False,
213 }
214 # Grok has ERROR status (unreadable store); all auto-chain backends MISSING.
215 with (
216 mock.patch.object(grok_x, "binary_path", return_value="/usr/bin/grok"),
217 mock.patch.object(grok_x, "has_stored_auth", return_value=False),
218 mock.patch.object(
219 grok_x, "stored_auth_status",
220 return_value=(grok_x.AUTH_ERROR, "store unreadable", None),
221 ),
222 mock.patch("lib.backends.which", return_value="/usr/bin/grok"),
223 mock.patch("lib.bird_x.get_bird_status", return_value=bird_status),
224 mock.patch("lib.bird_x.is_bird_installed", return_value=False),
225 mock.patch("lib.xurl_x.has_stored_auth", return_value=False),
226 ):
227 record = doctor._x_record(config)
228 # Must NOT be tier error / NOT WORKING.
229 assert record["tier"] != "error", "grok ERROR unpinned must not be tier error"
230 # Should be unconfigured (tier off).
231 assert record["status"] == "unconfigured"
232 assert record["tier"] == "off"
233 # Note should mention grok is unused/opt-in; should NOT prescribe grok login.
234 assert "grok" in record["note"].lower()
235 assert "opt-in" in record["note"].lower() or "unused" in record["note"].lower()
236 # Fix should be empty (no grok login prescription for unused opt-in).
237 assert record["fix"] == ""
238
239
240 def test_doctor_grok_store_with_pending_bird_predicts_bird():
241 """Unpinned + grok store + pending browser auth -> doctor predicts bird.
242
243 When bird is installed and browser-cookie extraction is configured,
244 doctor should predict bird (pending-cookie usable), NOT report X as
245 unconfigured due to an unused grok store. Pending bird takes precedence.
246 """
247 from lib import env, grok_x
248
249 config = {} # No pin, no static AUTH_TOKEN/CT0
250 bird_status = {
251 "installed": True, # Bird IS installed
252 "authenticated": False, # No static cookies in config
253 "username": "",
254 "can_install": True,
255 }
256 # Grok has OK status; bird is pending (installed, FROM_BROWSER configured).
257 with (
258 mock.patch.object(grok_x, "binary_path", return_value="/usr/bin/grok"),
259 mock.patch.object(grok_x, "has_stored_auth", return_value=True),
260 mock.patch.object(grok_x, "stored_auth_status", return_value=(grok_x.AUTH_OK, "", None)),
261 mock.patch("lib.backends.which", return_value="/usr/bin/grok"),
262 mock.patch("lib.bird_x.get_bird_status", return_value=bird_status),
263 mock.patch("lib.bird_x.is_bird_installed", return_value=True),
264 mock.patch("lib.xurl_x.has_stored_auth", return_value=False),
265 # Simulate x_pending_browser_auth returning True (bird pending)
266 mock.patch.object(env, "x_pending_browser_auth", return_value=True),
267 ):
268 record = doctor._x_record(config)
269 # Doctor should predict bird (pending), NOT report X as unconfigured.
270 assert record["tier"] != "off", "pending bird should not be tier off"
271 assert record["status"] != "unconfigured", "pending bird should not be unconfigured"
272 # Should be OK tier with bird prediction.
273 assert record["tier"] == "ok"
274 assert record["status"] == health.OK
275 assert "bird" in record["note"].lower()
276 assert "browser cookies" in record["note"].lower() or "cookie" in record["note"].lower()
277
278
279 def test_doctor_grok_does_not_hide_xurl_error():
280 """Unpinned + grok store + xurl ERROR -> doctor keeps xurl error, not unconfigured.
281
282 When an auto-chain backend (xurl) is configured but broken, doctor must
283 report that error with its repair guidance. Unused grok must NOT swallow
284 the genuine auto-chain failure.
285 """
286 from lib import backends as _backends
287 from lib import grok_x
288
289 config = {} # No pin
290 bird_status = {
291 "installed": False,
292 "authenticated": False,
293 "username": "",
294 "can_install": False,
295 }
296
297 # Mock xurl probe to return ERROR status
298 def mock_probe_xurl(config):
299 return _backends.BackendFinding(
300 name="xurl",
301 status=health.ERROR,
302 detail="store unreadable",
303 prescription="xurl auth oauth2 login",
304 requires="xurl CLI installed + OAuth2 login",
305 )
306
307 # Intercept _run_probe to inject xurl ERROR
308 original_run_probe = _backends._run_probe
309
310 def patched_run_probe(spec, config):
311 if spec.name == "xurl":
312 return mock_probe_xurl(config)
313 return original_run_probe(spec, config)
314
315 # Grok has OK status; xurl has ERROR (unreadable store).
316 # All other auto-chain backends are MISSING.
317 with (
318 mock.patch.object(grok_x, "binary_path", return_value="/usr/bin/grok"),
319 mock.patch.object(grok_x, "has_stored_auth", return_value=True),
320 mock.patch.object(grok_x, "stored_auth_status", return_value=(grok_x.AUTH_OK, "", None)),
321 mock.patch("lib.backends.which", side_effect=lambda cmd: "/usr/bin/grok" if cmd == "grok" else None),
322 mock.patch("lib.bird_x.get_bird_status", return_value=bird_status),
323 mock.patch("lib.bird_x.is_bird_installed", return_value=False),
324 mock.patch.object(_backends, "_run_probe", patched_run_probe),
325 ):
326 record = doctor._x_record(config)
327
328 # Doctor should NOT report X as unconfigured.
329 assert record["tier"] != "off", "xurl error must not be hidden by unused grok"
330 assert record["status"] != "unconfigured", "xurl error must not become unconfigured"
331 # Should keep the error tier and xurl repair guidance.
332 assert record["tier"] == "error"
333 # The fix should contain xurl repair guidance, not be empty.
334 assert record["fix"], "xurl repair guidance must not be cleared"
335 assert "xurl" in record["fix"].lower() or "oauth" in record["fix"].lower()
336
337
338 def test_doctor_pending_bird_does_not_hide_xurl_error():
339 """Unpinned + FROM_BROWSER + bird installed + xurl ERROR -> keeps xurl error.
340
341 Pending bird must NOT replace a record with a configured auto-chain backend
342 in ERROR. Doctor should report the xurl error with its repair guidance,
343 NOT report X as OK via pending bird.
344 """
345 from lib import backends as _backends
346 from lib import env, grok_x
347
348 config = {} # No pin, no static AUTH_TOKEN/CT0
349 bird_status = {
350 "installed": True, # Bird IS installed (for pending bird)
351 "authenticated": False, # No static cookies
352 "username": "",
353 "can_install": True,
354 }
355
356 # Mock xurl probe to return ERROR status
357 def mock_probe_xurl(config):
358 return _backends.BackendFinding(
359 name="xurl",
360 status=health.ERROR,
361 detail="store unreadable",
362 prescription="xurl auth oauth2 login",
363 requires="xurl CLI installed + OAuth2 login",
364 )
365
366 original_run_probe = _backends._run_probe
367
368 def patched_run_probe(spec, config):
369 if spec.name == "xurl":
370 return mock_probe_xurl(config)
371 return original_run_probe(spec, config)
372
373 # x_pending_browser_auth returns True (bird pending), but xurl has ERROR.
374 with (
375 mock.patch.object(grok_x, "binary_path", return_value=None),
376 mock.patch.object(grok_x, "has_stored_auth", return_value=False),
377 mock.patch("lib.backends.which", return_value=None),
378 mock.patch("lib.bird_x.get_bird_status", return_value=bird_status),
379 mock.patch("lib.bird_x.is_bird_installed", return_value=True),
380 mock.patch.object(env, "x_pending_browser_auth", return_value=True),
381 mock.patch.object(_backends, "_run_probe", patched_run_probe),
382 ):
383 record = doctor._x_record(config)
384
385 # Doctor should NOT report X as OK via pending bird.
386 assert record["tier"] != "ok", "xurl error must not be hidden by pending bird"
387 assert record["status"] != health.OK, "xurl error must not become OK"
388 # Should keep the error tier and xurl repair guidance.
389 assert record["tier"] == "error"
390 assert record["fix"], "xurl repair guidance must not be cleared"
391 assert "xurl" in record["fix"].lower() or "oauth" in record["fix"].lower()
392
392 lines PYTHON