返回 last30days-skill
test_footer_nudge_suppression.py
根目录 / tests / test_footer_nudge_suppression.py
1 """Tests for the BRAVE/SERPER web-promo suppression when hosting-model-driven."""
2
3 from __future__ import annotations
4
5 import os
6 import subprocess
7 import sys
8 import tempfile
9 import unittest
10 from pathlib import Path
11
12 REPO_ROOT = Path(__file__).resolve().parents[1]
13
14
15 def _engine() -> Path:
16 return REPO_ROOT / "skills" / "last30days" / "scripts" / "last30days.py"
17
18
19 class FooterNudgeSuppressionTests(unittest.TestCase):
20 def _run(self, *argv: str, topic: str) -> subprocess.CompletedProcess:
21 cmd = [
22 sys.executable,
23 str(_engine()),
24 topic,
25 "--mock",
26 "--emit=md",
27 *argv,
28 ]
29 env = {
30 **os.environ,
31 "LAST30DAYS_SKIP_PREFLIGHT": "1",
32 # Skip ~/.config/last30days/.env so a contributor's saved
33 # BRAVE/EXA/SERPER/PARALLEL key doesn't make grounding "available"
34 # and suppress the promo we're checking for.
35 "LAST30DAYS_CONFIG_DIR": "",
36 # Keychain is a THIRD credential source, independent of the env
37 # stripping below and of LAST30DAYS_CONFIG_DIR. On a contributor's
38 # Mac a stored `last30days-BRAVE_API_KEY` item sets
39 # native_web_backend, _missing_sources_for_promo returns None, and
40 # this test fails for a reason that has nothing to do with promo
41 # selection. Seal that source too.
42 "LAST30DAYS_SKIP_KEYCHAIN": "1",
43 # Pin X as available so _missing_sources_for_promo selects "web"
44 # (otherwise the "x" promo wins and the BRAVE_API_KEY string never
45 # appears).
46 "XAI_API_KEY": "test-stub",
47 }
48 # Strip any grounded-web keys the host might have so the promo path
49 # triggers deterministically in mock + no-backend. Also strip X cookie
50 # credentials so XAI_API_KEY is the unambiguous X backend.
51 for key in ("BRAVE_API_KEY", "EXA_API_KEY", "SERPER_API_KEY",
52 "PARALLEL_API_KEY", "OPENROUTER_API_KEY", "PERPLEXITY_API_KEY",
53 "AUTH_TOKEN", "CT0", "LAST30DAYS_X_BACKEND"):
54 env.pop(key, None)
55 # Run from a tmpdir so _find_project_env() can't walk up into any
56 # .claude/last30days.env above the repo on the contributor's machine.
57 with tempfile.TemporaryDirectory() as tmp:
58 # pass(1) is a FOURTH credential source (_load_pass): a stored
59 # last30days/BRAVE_API_KEY entry leaks in exactly like the
60 # Keychain item sealed above. pass honors PASSWORD_STORE_DIR, so
61 # point it at an empty store inside the tmpdir — every lookup
62 # misses without touching the contributor's real store.
63 empty_pass_store = Path(tmp) / "empty-pass-store"
64 empty_pass_store.mkdir()
65 env["PASSWORD_STORE_DIR"] = str(empty_pass_store)
66 return subprocess.run(
67 cmd, capture_output=True, text=True, encoding="utf-8", env=env, cwd=tmp,
68 )
69
70 def test_bare_run_emits_web_promo(self):
71 result = self._run(topic="OpenAI")
72 combined = result.stdout + result.stderr
73 # Mock mode still shows the promo when nothing indicates a hosting
74 # model is driving. Check both streams since the UI may emit to stderr.
75 self.assertIn("BRAVE_API_KEY", combined)
76
77 def test_competitors_plan_suppresses_web_promo(self):
78 result = self._run(
79 "--competitors-list", "Anthropic",
80 "--competitors-plan",
81 '{"Anthropic":{"x_handle":"AnthropicAI","subreddits":["ClaudeAI"]}}',
82 topic="OpenAI",
83 )
84 combined = result.stdout + result.stderr
85 self.assertNotIn(
86 "unlock native grounded web search",
87 combined,
88 msg="web promo should be suppressed when --competitors-plan is passed",
89 )
90
91 def test_plan_suppresses_web_promo(self):
92 plan = (
93 '{"intent":"concept","freshness_mode":"balanced_recent",'
94 '"cluster_mode":"none","subqueries":[{"label":"primary",'
95 '"search_query":"OpenAI","ranking_query":"OpenAI",'
96 '"sources":["grounding"]}],"source_weights":{"grounding":1.0}}'
97 )
98 result = self._run("--plan", plan, topic="OpenAI")
99 combined = result.stdout + result.stderr
100 self.assertNotIn(
101 "unlock native grounded web search",
102 combined,
103 msg="web promo should be suppressed when --plan is passed",
104 )
105
106 if __name__ == "__main__":
107 unittest.main()
108
108 lines PYTHON