返回 last30days-skill
test_agentcookie.py
根目录 / tests / test_agentcookie.py
1 """U1/U5: agentcookie sidecar reader (lib/agentcookie.py).
2
3 Subprocess is always mocked; only obvious dummy cookie values are used
4 (test-auth-token / test-ct0). Enforces the soft-dep, AGENTCOOKIE=off,
5 FROM_BROWSER-independence, complete-pair, and no-value-logging contracts.
6 """
7
8 import json
9 import subprocess
10 from unittest import mock
11
12 from lib import agentcookie
13
14 _DUMMY = {"auth_token": "test-auth-token", "ct0": "test-ct0"}
15
16
17 def _run_ok(stdout):
18 return mock.Mock(returncode=0, stdout=stdout, stderr="")
19
20
21 def test_off_disables_without_subprocess():
22 with (
23 mock.patch("shutil.which", return_value="/usr/bin/agentcookie"),
24 mock.patch("subprocess.run", side_effect=AssertionError("should not run")),
25 ):
26 assert agentcookie.read_x_cookies({"AGENTCOOKIE": "off"}) is None
27 assert agentcookie.is_disabled({"AGENTCOOKIE": "off"}) is True
28 assert agentcookie.is_available({"AGENTCOOKIE": "off"}) is False
29
30
31 def test_absent_binary_is_soft_skip():
32 with mock.patch("shutil.which", return_value=None):
33 assert agentcookie.read_x_cookies({}) is None
34 assert agentcookie.is_available({}) is False
35
36
37 def test_complete_pair_from_list_json():
38 payload = json.dumps([
39 {"name": "auth_token", "value": "test-auth-token", "domain": ".x.com"},
40 {"name": "ct0", "value": "test-ct0", "domain": ".x.com"},
41 {"name": "guest_id", "value": "irrelevant", "domain": ".x.com"},
42 ])
43 with (
44 mock.patch("shutil.which", return_value="/usr/bin/agentcookie"),
45 mock.patch("subprocess.run", return_value=_run_ok(payload)) as run,
46 ):
47 result = agentcookie.read_x_cookies({})
48 assert result == _DUMMY
49 # It asks for the x.com domain in JSON.
50 args = run.call_args[0][0]
51 assert "--domain" in args and ".x.com" in args and "--json" in args
52
53
54 def test_complete_pair_from_cookies_wrapper():
55 payload = json.dumps({"cookies": [
56 {"name": "auth_token", "value": "test-auth-token"},
57 {"name": "ct0", "value": "test-ct0"},
58 ]})
59 with (
60 mock.patch("shutil.which", return_value="/usr/bin/agentcookie"),
61 mock.patch("subprocess.run", return_value=_run_ok(payload)),
62 ):
63 assert agentcookie.read_x_cookies({}) == _DUMMY
64
65
66 def test_complete_pair_from_flat_mapping():
67 payload = json.dumps({"auth_token": "test-auth-token", "ct0": "test-ct0"})
68 with (
69 mock.patch("shutil.which", return_value="/usr/bin/agentcookie"),
70 mock.patch("subprocess.run", return_value=_run_ok(payload)),
71 ):
72 assert agentcookie.read_x_cookies({}) == _DUMMY
73
74
75 def test_half_pair_is_rejected():
76 payload = json.dumps([{"name": "auth_token", "value": "test-auth-token"}])
77 with (
78 mock.patch("shutil.which", return_value="/usr/bin/agentcookie"),
79 mock.patch("subprocess.run", return_value=_run_ok(payload)),
80 ):
81 assert agentcookie.read_x_cookies({}) is None
82
83
84 def test_nonzero_exit_is_none():
85 with (
86 mock.patch("shutil.which", return_value="/usr/bin/agentcookie"),
87 mock.patch("subprocess.run", return_value=mock.Mock(returncode=2, stdout="", stderr="nope")),
88 ):
89 assert agentcookie.read_x_cookies({}) is None
90
91
92 def test_timeout_is_none():
93 with (
94 mock.patch("shutil.which", return_value="/usr/bin/agentcookie"),
95 mock.patch("subprocess.run", side_effect=subprocess.TimeoutExpired("agentcookie", 10)),
96 ):
97 assert agentcookie.read_x_cookies({}) is None
98
99
100 def test_non_json_is_none():
101 with (
102 mock.patch("shutil.which", return_value="/usr/bin/agentcookie"),
103 mock.patch("subprocess.run", return_value=_run_ok("<html>blocked</html>")),
104 ):
105 assert agentcookie.read_x_cookies({}) is None
106
107
108 def test_never_logs_cookie_values():
109 payload = json.dumps({"auth_token": "test-auth-token", "ct0": "test-ct0"})
110 logged = []
111 with (
112 mock.patch("shutil.which", return_value="/usr/bin/agentcookie"),
113 mock.patch("subprocess.run", return_value=_run_ok(payload)),
114 mock.patch("lib.agentcookie.log.source_log", lambda src, msg, **k: logged.append(msg)),
115 ):
116 agentcookie.read_x_cookies({})
117 joined = "\n".join(logged)
118 assert "test-auth-token" not in joined
119 assert "test-ct0" not in joined
120
121
122 # --- role detection (subprocess-free config read) --------------------------
123
124
125 def test_role_is_sink_reads_config_file(tmp_path):
126 cfg = tmp_path / "config.json"
127 cfg.write_text(json.dumps({"role": "sink"}))
128 config = {"AGENTCOOKIE_CONFIG": str(cfg)}
129 # No subprocess is spawned to classify the role.
130 with mock.patch("subprocess.run", side_effect=AssertionError("no subprocess for role")):
131 assert agentcookie.role(config) == "sink"
132 assert agentcookie.role_is_sink(config) is True
133
134
135 def test_role_source_is_not_sink(tmp_path):
136 cfg = tmp_path / "config.json"
137 cfg.write_text(json.dumps({"role": "source"}))
138 config = {"AGENTCOOKIE_CONFIG": str(cfg)}
139 assert agentcookie.role_is_sink(config) is False
140
141
142 def test_role_missing_or_unparsable_is_not_sink(tmp_path):
143 missing = {"AGENTCOOKIE_CONFIG": str(tmp_path / "nope.json")}
144 assert agentcookie.role_is_sink(missing) is False
145 bad = tmp_path / "bad.json"
146 bad.write_text("not json {")
147 assert agentcookie.role_is_sink({"AGENTCOOKIE_CONFIG": str(bad)}) is False
148
148 lines PYTHON