返回 last30days-skill
test_diagnose_browser_pending.py
根目录 / tests / test_diagnose_browser_pending.py
1 """Tests for the --diagnose / --preflight browser-auth pending signal.
2
3 `--diagnose` and `--preflight` load config in `plan_only` mode, which skips
4 browser-cookie extraction (no Keychain popup). Before this fix that made X drop
5 out of `available_sources` whenever auth came from FROM_BROWSER, even though a
6 real run authenticates X fine — a false-negative that sent a debugging session
7 down a 30-minute wrong path. `env.x_pending_browser_auth` reports the
8 "available pending browser auth" state without reading any cookie, and
9 `pipeline.diagnose` / `pipeline.available_sources` surface it.
10 """
11
12 from unittest import mock
13
14 from lib import env, pipeline
15
16
17 def _cfg(**over):
18 cfg = {"_BROWSER_COOKIE_MODE": "plan_only"}
19 cfg.update(over)
20 return cfg
21
22
23 class TestXPendingBrowserAuth:
24 """The no-read predicate in env.py."""
25
26 def test_pending_true_with_from_browser_and_bird(self):
27 cfg = _cfg(FROM_BROWSER="chrome")
28 with mock.patch("lib.env.get_x_source", return_value=None), \
29 mock.patch("lib.bird_x.is_bird_installed", return_value=True):
30 assert env.x_pending_browser_auth(cfg) is True
31
32 def test_false_when_no_browser_resolved(self):
33 # FROM_BROWSER=off -> cookie_extraction_browsers() returns [].
34 cfg = _cfg(FROM_BROWSER="off")
35 with mock.patch("lib.env.get_x_source", return_value=None), \
36 mock.patch("lib.bird_x.is_bird_installed", return_value=True):
37 assert env.x_pending_browser_auth(cfg) is False
38
39 def test_false_when_from_browser_unset(self):
40 cfg = _cfg()
41 with mock.patch("lib.env.get_x_source", return_value=None), \
42 mock.patch("lib.bird_x.is_bird_installed", return_value=True):
43 assert env.x_pending_browser_auth(cfg) is False
44
45 def test_false_when_x_available_outright(self):
46 # Static bird creds / xurl / xquik -> get_x_source truthy -> not pending.
47 cfg = _cfg(FROM_BROWSER="chrome")
48 with mock.patch("lib.env.get_x_source", return_value="bird"):
49 assert env.x_pending_browser_auth(cfg) is False
50
51 def test_false_when_xai_key_present(self):
52 # Real get_x_source path: an xAI key makes X available outright.
53 cfg = _cfg(FROM_BROWSER="chrome", XAI_API_KEY="dummy-not-real")
54 assert env.x_pending_browser_auth(cfg) is False
55
56 def test_false_when_bird_not_installed(self):
57 cfg = _cfg(FROM_BROWSER="chrome")
58 with mock.patch("lib.env.get_x_source", return_value=None), \
59 mock.patch("lib.bird_x.is_bird_installed", return_value=False):
60 assert env.x_pending_browser_auth(cfg) is False
61
62 def test_false_in_read_mode(self):
63 # A real run (mode=read) has already attempted extraction; its status
64 # must be unchanged, never "pending".
65 cfg = _cfg(FROM_BROWSER="chrome", _BROWSER_COOKIE_MODE="read")
66 with mock.patch("lib.env.get_x_source", return_value=None), \
67 mock.patch("lib.bird_x.is_bird_installed", return_value=True):
68 assert env.x_pending_browser_auth(cfg) is False
69
70 def test_reads_no_cookies(self):
71 # R3: the predicate must never read cookie values or hit Keychain.
72 cfg = _cfg(FROM_BROWSER="chrome")
73 with mock.patch("lib.env.get_x_source", return_value=None), \
74 mock.patch("lib.bird_x.is_bird_installed", return_value=True), \
75 mock.patch("lib.env.extract_browser_credentials",
76 side_effect=AssertionError("must not read cookies")), \
77 mock.patch("lib.cookie_extract.extract_cookies",
78 side_effect=AssertionError("must not read cookies")):
79 assert env.x_pending_browser_auth(cfg) is True
80
81
82 class TestDiagnoseSurfacesPending:
83 """available_sources + diagnose consume the predicate."""
84
85 def test_diagnose_includes_pending_x_and_flag(self):
86 cfg = _cfg(FROM_BROWSER="chrome")
87 with mock.patch("lib.env.get_x_source", return_value=None), \
88 mock.patch("lib.bird_x.is_bird_installed", return_value=True):
89 diag = pipeline.diagnose(cfg, safe=True)
90 assert "x" in diag["available_sources"]
91 assert diag["x_pending_browser_auth"] is True
92 # The safe contract is preserved: no cookie values read.
93 assert diag["browser_cookies"]["reads_values"] is False
94
95 def test_diagnose_excludes_x_when_browser_off(self):
96 cfg = _cfg(FROM_BROWSER="off")
97 with mock.patch("lib.env.get_x_source", return_value=None), \
98 mock.patch("lib.bird_x.is_bird_installed", return_value=True):
99 diag = pipeline.diagnose(cfg, safe=True)
100 assert "x" not in diag["available_sources"]
101 assert diag["x_pending_browser_auth"] is False
102
103 def test_diagnose_x_outright_not_pending(self):
104 cfg = _cfg(FROM_BROWSER="chrome")
105 with mock.patch("lib.env.get_x_source", return_value="bird"):
106 diag = pipeline.diagnose(cfg, safe=True)
107 assert "x" in diag["available_sources"]
108 assert diag["x_pending_browser_auth"] is False
109
110 def test_available_sources_no_double_x(self):
111 # When X is available outright, the else branch must not also append a 2nd "x".
112 cfg = _cfg(FROM_BROWSER="chrome")
113 with mock.patch("lib.env.get_x_source", return_value="bird"):
114 sources = pipeline.available_sources(cfg)
115 assert sources.count("x") == 1
116
117 def test_available_sources_uses_precomputed_x_pending(self):
118 # diagnose() passes x_pending in to avoid a second predicate evaluation;
119 # when provided, available_sources must not call the predicate itself.
120 cfg = _cfg(FROM_BROWSER="chrome")
121 with mock.patch("lib.env.get_x_source", return_value=None), \
122 mock.patch("lib.env.x_pending_browser_auth",
123 side_effect=AssertionError("must use precomputed x_pending")):
124 assert "x" in pipeline.available_sources(cfg, x_pending=True)
125 assert "x" not in pipeline.available_sources(cfg, x_pending=False)
126
127 def test_diagnose_evaluates_predicate_once(self):
128 # The tidy: x_pending_browser_auth runs exactly once per diagnose() call.
129 cfg = _cfg(FROM_BROWSER="chrome")
130 with mock.patch("lib.env.get_x_source", return_value=None), \
131 mock.patch("lib.bird_x.is_bird_installed", return_value=True), \
132 mock.patch("lib.env.x_pending_browser_auth", wraps=env.x_pending_browser_auth) as spy:
133 diag = pipeline.diagnose(cfg, safe=True)
134 assert spy.call_count == 1
135 assert "x" in diag["available_sources"]
136 assert diag["x_pending_browser_auth"] is True
137
137 lines PYTHON