claude-dev-env 8.43.4 → 8.43.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_shared/advisor/advisor-protocol.md +2 -2
- package/_shared/advisor/reference/cli-chain.md +9 -12
- package/_shared/advisor/reference/lifecycle.md +1 -1
- package/_shared/advisor/reference/third-party-bind.md +3 -3
- package/bin/install.mjs +39 -3
- package/bin/install.shared-settings.test.mjs +46 -0
- package/bin/install.test.mjs +1 -1
- package/docs/second-claude-account.md +11 -13
- package/package.json +1 -1
- package/scripts/_code_review_test_support.py +29 -39
- package/scripts/claude_account_worker.py +46 -297
- package/scripts/claude_account_worker_process.py +18 -154
- package/scripts/claude_account_worker_report.py +8 -4
- package/scripts/claude_chain_usage.py +7 -271
- package/scripts/codec_forwarding_test_support.py +11 -65
- package/scripts/dev_env_scripts_constants/claude_account_worker_constants.py +6 -24
- package/scripts/invoke_code_review.py +34 -56
- package/scripts/policy_lint/config/approved_test_pairs.py +1 -0
- package/scripts/resolve_worker_spawn.py +40 -72
- package/scripts/test_account_broker_guard.py +0 -4
- package/scripts/test_claude_account_worker.py +105 -350
- package/scripts/test_claude_chain_usage.py +50 -510
- package/scripts/test_dispatcher_profile_import.py +5 -9
- package/scripts/test_invoke_code_review.py +7 -6
- package/scripts/test_invoke_code_review_chain.py +26 -0
- package/scripts/test_invoke_code_review_cli.py +8 -8
- package/scripts/test_invoke_code_review_codec.py +3 -11
- package/scripts/test_invoke_code_review_contract.py +5 -1
- package/scripts/test_resolve_worker_spawn.py +118 -265
- package/scripts/test_resolve_worker_spawn_codec.py +9 -21
- package/scripts/tests/test_code_review_constants.py +2 -2
- package/scripts/tests/test_policy_lint_rules_pairing.py +13 -0
- package/scripts/claude_account_choice.py +0 -422
- package/scripts/claude_chain_runner.py +0 -1289
- package/scripts/test_claude_account_choice.py +0 -355
- package/scripts/test_claude_chain_runner.py +0 -1839
|
@@ -1,568 +1,107 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Tests for Claude meter probes used by the account broker."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import json
|
|
6
5
|
import subprocess
|
|
7
6
|
import sys
|
|
8
|
-
|
|
7
|
+
import urllib.error
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
|
|
11
11
|
import pytest
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
sys.path.insert(0, str(_SCRIPTS_DIR))
|
|
16
|
-
|
|
17
|
-
import claude_chain_runner as chain_runner # noqa: E402
|
|
18
|
-
import claude_chain_usage as usage # noqa: E402
|
|
19
|
-
from dev_env_scripts_constants.claude_chain_constants import ( # noqa: E402
|
|
20
|
-
CHAIN_CONFIG_ERROR_EXIT_CODE,
|
|
21
|
-
CONFIG_CHAIN_KEY,
|
|
22
|
-
CONFIG_COMMAND_KEY,
|
|
23
|
-
CONFIG_CREDENTIALS_PATH_KEY,
|
|
24
|
-
CONFIG_EXTRA_ARGS_KEY,
|
|
25
|
-
CONFIG_FILENAME,
|
|
26
|
-
UTF8_ENCODING,
|
|
27
|
-
)
|
|
28
|
-
from dev_env_scripts_constants.claude_chain_usage_constants import ( # noqa: E402
|
|
29
|
-
CLI_CONFIG_PATH_FLAG,
|
|
30
|
-
FULL_WEEKLY_PERCENT,
|
|
31
|
-
JSON_ACCOUNTS_KEY,
|
|
32
|
-
JSON_COMMAND_KEY,
|
|
33
|
-
JSON_ERROR_KEY,
|
|
34
|
-
JSON_WEEKLY_REMAINING_PERCENT_KEY,
|
|
35
|
-
RESOLVE_USAGE_WINDOW_MODULE_NAME,
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
PLACEHOLDER_CREDENTIALS_PRIMARY = "/path/to/account-primary/.credentials.json"
|
|
39
|
-
PLACEHOLDER_CREDENTIALS_SECONDARY = "/path/to/account-secondary/.credentials.json"
|
|
40
|
-
PLACEHOLDER_CREDENTIALS_TERTIARY = "/path/to/account-tertiary/.credentials.json"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def _entry(
|
|
44
|
-
command: str,
|
|
45
|
-
*,
|
|
46
|
-
extra_args: list[str] | None = None,
|
|
47
|
-
credentials_path: str | None = None,
|
|
48
|
-
) -> dict[str, object]:
|
|
49
|
-
chain_entry: dict[str, object] = {
|
|
50
|
-
CONFIG_COMMAND_KEY: command,
|
|
51
|
-
CONFIG_EXTRA_ARGS_KEY: extra_args if extra_args is not None else [],
|
|
52
|
-
}
|
|
53
|
-
if credentials_path is not None:
|
|
54
|
-
chain_entry[CONFIG_CREDENTIALS_PATH_KEY] = credentials_path
|
|
55
|
-
return chain_entry
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def _write_chain_config(
|
|
59
|
-
tmp_path: Path, all_chain_entries: list[dict[str, object]]
|
|
60
|
-
) -> Path:
|
|
61
|
-
config_file = tmp_path / CONFIG_FILENAME
|
|
62
|
-
config_file.write_text(
|
|
63
|
-
json.dumps({CONFIG_CHAIN_KEY: all_chain_entries}), encoding=UTF8_ENCODING
|
|
64
|
-
)
|
|
65
|
-
return config_file
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def _probe_from_utilization_by_path(
|
|
69
|
-
utilization_by_path: dict[str, float | Exception],
|
|
70
|
-
) -> usage.WeeklyUtilizationProbe:
|
|
71
|
-
def active_probe(credentials_path: Path) -> float:
|
|
72
|
-
path_key = credentials_path.as_posix()
|
|
73
|
-
probe_outcome = utilization_by_path[path_key]
|
|
74
|
-
if isinstance(probe_outcome, Exception):
|
|
75
|
-
raise probe_outcome
|
|
76
|
-
return probe_outcome
|
|
77
|
-
|
|
78
|
-
return active_probe
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def test_remaining_percent_is_full_scale_minus_utilization(
|
|
82
|
-
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
83
|
-
) -> None:
|
|
84
|
-
config_file = _write_chain_config(
|
|
85
|
-
tmp_path,
|
|
86
|
-
[
|
|
87
|
-
_entry("claude", credentials_path=PLACEHOLDER_CREDENTIALS_PRIMARY),
|
|
88
|
-
_entry("claude-profile-c", credentials_path=PLACEHOLDER_CREDENTIALS_SECONDARY),
|
|
89
|
-
],
|
|
90
|
-
)
|
|
91
|
-
monkeypatch.setattr(
|
|
92
|
-
usage,
|
|
93
|
-
"weekly_utilization_probe",
|
|
94
|
-
_probe_from_utilization_by_path(
|
|
95
|
-
{
|
|
96
|
-
PLACEHOLDER_CREDENTIALS_PRIMARY: 42.0,
|
|
97
|
-
PLACEHOLDER_CREDENTIALS_SECONDARY: 9.0,
|
|
98
|
-
}
|
|
99
|
-
),
|
|
100
|
-
)
|
|
101
|
-
all_reports = usage.report_chain_weekly_usage(config_path=config_file)
|
|
102
|
-
assert [each_report.command for each_report in all_reports] == [
|
|
103
|
-
"claude",
|
|
104
|
-
"claude-profile-c",
|
|
105
|
-
]
|
|
106
|
-
assert all_reports[0].weekly_remaining_percent == pytest.approx(
|
|
107
|
-
FULL_WEEKLY_PERCENT - 42.0
|
|
108
|
-
)
|
|
109
|
-
assert all_reports[1].weekly_remaining_percent == pytest.approx(
|
|
110
|
-
FULL_WEEKLY_PERCENT - 9.0
|
|
111
|
-
)
|
|
112
|
-
assert all_reports[0].error is None
|
|
113
|
-
assert all_reports[1].error is None
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
def test_rank_orders_by_remaining_desc_preserves_ties_and_puts_nulls_last() -> None:
|
|
117
|
-
all_reports = [
|
|
118
|
-
usage.AccountUsageReport(command="first", weekly_remaining_percent=40.0),
|
|
119
|
-
usage.AccountUsageReport(command="second", weekly_remaining_percent=70.0),
|
|
120
|
-
usage.AccountUsageReport(
|
|
121
|
-
command="third", weekly_remaining_percent=None, error="probe failed"
|
|
122
|
-
),
|
|
123
|
-
usage.AccountUsageReport(command="fourth", weekly_remaining_percent=70.0),
|
|
124
|
-
usage.AccountUsageReport(
|
|
125
|
-
command="fifth", weekly_remaining_percent=None, error="no token"
|
|
126
|
-
),
|
|
127
|
-
usage.AccountUsageReport(command="sixth", weekly_remaining_percent=10.0),
|
|
128
|
-
]
|
|
129
|
-
ranked_reports = usage.rank_accounts_by_weekly_remaining(all_reports)
|
|
130
|
-
assert [each_report.command for each_report in ranked_reports] == [
|
|
131
|
-
"second",
|
|
132
|
-
"fourth",
|
|
133
|
-
"first",
|
|
134
|
-
"sixth",
|
|
135
|
-
"third",
|
|
136
|
-
"fifth",
|
|
137
|
-
]
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
def test_probe_failure_yields_null_remaining_and_error_string(
|
|
141
|
-
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
142
|
-
) -> None:
|
|
143
|
-
config_file = _write_chain_config(
|
|
144
|
-
tmp_path,
|
|
145
|
-
[
|
|
146
|
-
_entry("claude", credentials_path=PLACEHOLDER_CREDENTIALS_PRIMARY),
|
|
147
|
-
_entry("claude-profile-c", credentials_path=PLACEHOLDER_CREDENTIALS_SECONDARY),
|
|
148
|
-
],
|
|
149
|
-
)
|
|
150
|
-
monkeypatch.setattr(
|
|
151
|
-
usage,
|
|
152
|
-
"weekly_utilization_probe",
|
|
153
|
-
_probe_from_utilization_by_path(
|
|
154
|
-
{
|
|
155
|
-
PLACEHOLDER_CREDENTIALS_PRIMARY: usage.WeeklyUtilizationProbeError(
|
|
156
|
-
"token expired"
|
|
157
|
-
),
|
|
158
|
-
PLACEHOLDER_CREDENTIALS_SECONDARY: 25.0,
|
|
159
|
-
}
|
|
160
|
-
),
|
|
161
|
-
)
|
|
162
|
-
all_reports = usage.report_chain_weekly_usage(config_path=config_file)
|
|
163
|
-
assert all_reports[0].weekly_remaining_percent is None
|
|
164
|
-
assert all_reports[0].error == "token expired"
|
|
165
|
-
assert all_reports[1].weekly_remaining_percent == pytest.approx(
|
|
166
|
-
FULL_WEEKLY_PERCENT - 25.0
|
|
167
|
-
)
|
|
168
|
-
assert all_reports[1].error is None
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
def test_missing_config_raises_chain_configuration_error(tmp_path: Path) -> None:
|
|
172
|
-
missing_config = tmp_path / "absent-chain.json"
|
|
173
|
-
with pytest.raises(chain_runner.ChainConfigurationError):
|
|
174
|
-
usage.report_chain_weekly_usage(config_path=missing_config)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
def test_empty_config_raises_chain_configuration_error(tmp_path: Path) -> None:
|
|
178
|
-
config_file = _write_chain_config(tmp_path, [])
|
|
179
|
-
with pytest.raises(chain_runner.ChainConfigurationError):
|
|
180
|
-
usage.report_chain_weekly_usage(config_path=config_file)
|
|
181
|
-
|
|
13
|
+
import claude_chain_usage as usage
|
|
14
|
+
from dev_env_scripts_constants.claude_chain_usage_constants import RESOLVE_USAGE_WINDOW_MODULE_NAME
|
|
182
15
|
|
|
183
|
-
def test_entry_without_credentials_path_uses_default(
|
|
184
|
-
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
185
|
-
) -> None:
|
|
186
|
-
default_path = Path("/path/to/default/.credentials.json")
|
|
187
|
-
monkeypatch.setattr(usage, "_default_credentials_path", lambda: default_path)
|
|
188
|
-
config_file = _write_chain_config(tmp_path, [_entry("claude")])
|
|
189
|
-
probed_paths: list[Path] = []
|
|
190
|
-
|
|
191
|
-
def active_probe(credentials_path: Path) -> float:
|
|
192
|
-
probed_paths.append(credentials_path)
|
|
193
|
-
return 10.0
|
|
194
|
-
|
|
195
|
-
monkeypatch.setattr(usage, "weekly_utilization_probe", active_probe)
|
|
196
|
-
all_reports = usage.report_chain_weekly_usage(config_path=config_file)
|
|
197
|
-
assert probed_paths == [default_path]
|
|
198
|
-
assert all_reports[0].weekly_remaining_percent == pytest.approx(
|
|
199
|
-
FULL_WEEKLY_PERCENT - 10.0
|
|
200
|
-
)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
def test_default_credentials_resolution_failure_yields_null_remaining(
|
|
204
|
-
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
205
|
-
) -> None:
|
|
206
|
-
def raise_probe_error() -> Path:
|
|
207
|
-
raise usage.WeeklyUtilizationProbeError("probe module not found")
|
|
208
|
-
|
|
209
|
-
monkeypatch.setattr(usage, "_default_credentials_path", raise_probe_error)
|
|
210
|
-
config_file = _write_chain_config(tmp_path, [_entry("claude")])
|
|
211
|
-
all_reports = usage.report_chain_weekly_usage(config_path=config_file)
|
|
212
|
-
assert all_reports[0].weekly_remaining_percent is None
|
|
213
|
-
assert all_reports[0].error == "probe module not found"
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
def test_entry_credentials_path_is_passed_to_probe(
|
|
217
|
-
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
218
|
-
) -> None:
|
|
219
|
-
config_file = _write_chain_config(
|
|
220
|
-
tmp_path,
|
|
221
|
-
[_entry("claude-profile-c", credentials_path=PLACEHOLDER_CREDENTIALS_TERTIARY)],
|
|
222
|
-
)
|
|
223
|
-
probed_paths: list[Path] = []
|
|
224
16
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
17
|
+
def test_probe_reads_both_meters_and_resets(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
18
|
+
session_reset = datetime(2026, 10, 4, tzinfo=timezone.utc)
|
|
19
|
+
weekly_reset = datetime(2026, 10, 7, tzinfo=timezone.utc)
|
|
228
20
|
|
|
229
|
-
|
|
230
|
-
usage.report_chain_weekly_usage(config_path=config_file)
|
|
231
|
-
assert probed_paths == [Path(PLACEHOLDER_CREDENTIALS_TERTIARY)]
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
def test_entry_credentials_path_expands_user_home(
|
|
235
|
-
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
236
|
-
) -> None:
|
|
237
|
-
tilde_credentials_path = "~/.claude-accounts/secondary/.credentials.json"
|
|
238
|
-
config_file = _write_chain_config(
|
|
239
|
-
tmp_path,
|
|
240
|
-
[_entry("claude-profile-c", credentials_path=tilde_credentials_path)],
|
|
241
|
-
)
|
|
242
|
-
probed_paths: list[Path] = []
|
|
243
|
-
|
|
244
|
-
def active_probe(credentials_path: Path) -> float:
|
|
245
|
-
probed_paths.append(credentials_path)
|
|
246
|
-
return 0.0
|
|
247
|
-
|
|
248
|
-
monkeypatch.setattr(usage, "weekly_utilization_probe", active_probe)
|
|
249
|
-
usage.report_chain_weekly_usage(config_path=config_file)
|
|
250
|
-
assert probed_paths == [Path(tilde_credentials_path).expanduser()]
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
def test_load_chain_carries_optional_credentials_path(tmp_path: Path) -> None:
|
|
254
|
-
config_file = _write_chain_config(
|
|
255
|
-
tmp_path,
|
|
256
|
-
[
|
|
257
|
-
_entry("claude"),
|
|
258
|
-
_entry("claude-profile-c", credentials_path=PLACEHOLDER_CREDENTIALS_SECONDARY),
|
|
259
|
-
],
|
|
260
|
-
)
|
|
261
|
-
all_entries = chain_runner.load_chain(config_file)
|
|
262
|
-
assert all_entries[0].credentials_path is None
|
|
263
|
-
assert all_entries[1].credentials_path == PLACEHOLDER_CREDENTIALS_SECONDARY
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
def test_invalid_credentials_path_type_raises(tmp_path: Path) -> None:
|
|
267
|
-
config_file = tmp_path / CONFIG_FILENAME
|
|
268
|
-
config_file.write_text(
|
|
269
|
-
json.dumps(
|
|
270
|
-
{
|
|
271
|
-
CONFIG_CHAIN_KEY: [
|
|
272
|
-
{
|
|
273
|
-
CONFIG_COMMAND_KEY: "claude",
|
|
274
|
-
CONFIG_EXTRA_ARGS_KEY: [],
|
|
275
|
-
CONFIG_CREDENTIALS_PATH_KEY: 12,
|
|
276
|
-
}
|
|
277
|
-
]
|
|
278
|
-
}
|
|
279
|
-
),
|
|
280
|
-
encoding=UTF8_ENCODING,
|
|
281
|
-
)
|
|
282
|
-
with pytest.raises(chain_runner.ChainConfigurationError):
|
|
283
|
-
chain_runner.load_chain(config_file)
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
def test_reports_to_json_payload_matches_cli_contract() -> None:
|
|
287
|
-
all_reports = [
|
|
288
|
-
usage.AccountUsageReport(command="claude", weekly_remaining_percent=58.0),
|
|
289
|
-
usage.AccountUsageReport(
|
|
290
|
-
command="claude-profile-c",
|
|
291
|
-
weekly_remaining_percent=None,
|
|
292
|
-
error="probe failed",
|
|
293
|
-
),
|
|
294
|
-
]
|
|
295
|
-
payload = usage.reports_to_json_payload(all_reports)
|
|
296
|
-
assert payload == {
|
|
297
|
-
JSON_ACCOUNTS_KEY: [
|
|
298
|
-
{
|
|
299
|
-
JSON_COMMAND_KEY: "claude",
|
|
300
|
-
JSON_WEEKLY_REMAINING_PERCENT_KEY: 58.0,
|
|
301
|
-
},
|
|
302
|
-
{
|
|
303
|
-
JSON_COMMAND_KEY: "claude-profile-c",
|
|
304
|
-
JSON_WEEKLY_REMAINING_PERCENT_KEY: None,
|
|
305
|
-
JSON_ERROR_KEY: "probe failed",
|
|
306
|
-
},
|
|
307
|
-
]
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
def test_cli_writes_json_accounts_report(
|
|
312
|
-
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
313
|
-
) -> None:
|
|
314
|
-
config_file = _write_chain_config(
|
|
315
|
-
tmp_path,
|
|
316
|
-
[
|
|
317
|
-
_entry("claude", credentials_path=PLACEHOLDER_CREDENTIALS_PRIMARY),
|
|
318
|
-
_entry("claude-profile-c", credentials_path=PLACEHOLDER_CREDENTIALS_SECONDARY),
|
|
319
|
-
],
|
|
320
|
-
)
|
|
321
|
-
monkeypatch.setattr(
|
|
322
|
-
usage,
|
|
323
|
-
"weekly_utilization_probe",
|
|
324
|
-
_probe_from_utilization_by_path(
|
|
325
|
-
{
|
|
326
|
-
PLACEHOLDER_CREDENTIALS_PRIMARY: 42.0,
|
|
327
|
-
PLACEHOLDER_CREDENTIALS_SECONDARY: usage.WeeklyUtilizationProbeError(
|
|
328
|
-
"no token"
|
|
329
|
-
),
|
|
330
|
-
}
|
|
331
|
-
),
|
|
332
|
-
)
|
|
333
|
-
exit_code = usage.main([CLI_CONFIG_PATH_FLAG, str(config_file)])
|
|
334
|
-
captured = capsys.readouterr()
|
|
335
|
-
assert exit_code == 0
|
|
336
|
-
assert captured.err == ""
|
|
337
|
-
payload = json.loads(captured.out)
|
|
338
|
-
assert payload == {
|
|
339
|
-
JSON_ACCOUNTS_KEY: [
|
|
340
|
-
{
|
|
341
|
-
JSON_COMMAND_KEY: "claude",
|
|
342
|
-
JSON_WEEKLY_REMAINING_PERCENT_KEY: FULL_WEEKLY_PERCENT - 42.0,
|
|
343
|
-
},
|
|
344
|
-
{
|
|
345
|
-
JSON_COMMAND_KEY: "claude-profile-c",
|
|
346
|
-
JSON_WEEKLY_REMAINING_PERCENT_KEY: None,
|
|
347
|
-
JSON_ERROR_KEY: "no token",
|
|
348
|
-
},
|
|
349
|
-
]
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
def test_cli_missing_config_prints_stderr_and_returns_config_error(
|
|
354
|
-
tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
|
355
|
-
) -> None:
|
|
356
|
-
missing_config = tmp_path / "absent-chain.json"
|
|
357
|
-
exit_code = usage.main([CLI_CONFIG_PATH_FLAG, str(missing_config)])
|
|
358
|
-
captured = capsys.readouterr()
|
|
359
|
-
assert exit_code == CHAIN_CONFIG_ERROR_EXIT_CODE
|
|
360
|
-
assert captured.out == ""
|
|
361
|
-
assert "not found" in captured.err.lower() or "claude chain" in captured.err.lower()
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
def test_weekly_remaining_from_utilization_uses_full_scale() -> None:
|
|
365
|
-
assert usage.weekly_remaining_from_utilization(9.0) == pytest.approx(91.0)
|
|
366
|
-
assert usage.weekly_remaining_from_utilization(0.0) == pytest.approx(
|
|
367
|
-
FULL_WEEKLY_PERCENT
|
|
368
|
-
)
|
|
369
|
-
assert usage.weekly_remaining_from_utilization(100.0) == pytest.approx(0.0)
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
def test_probe_weekly_utilization_reuses_resolver_windows(
|
|
373
|
-
monkeypatch: pytest.MonkeyPatch,
|
|
374
|
-
) -> None:
|
|
375
|
-
class _FakeWindows:
|
|
376
|
-
session_utilization = None
|
|
377
|
-
session_resets_at = None
|
|
378
|
-
weekly_utilization = 37.5
|
|
379
|
-
weekly_resets_at = None
|
|
380
|
-
|
|
381
|
-
class _FakeResolver:
|
|
382
|
-
def read_oauth_access_token(self, credentials_path: Path, now: object) -> str:
|
|
383
|
-
assert credentials_path == Path(PLACEHOLDER_CREDENTIALS_PRIMARY)
|
|
384
|
-
return "token-value"
|
|
385
|
-
|
|
386
|
-
def resolve_access_token(self, credentials_path: Path, now: object) -> str:
|
|
387
|
-
raise AssertionError("probe must not fall back to session ingress")
|
|
388
|
-
|
|
389
|
-
def _fetch_usage_payload(self, access_token: str) -> dict[str, object]:
|
|
390
|
-
assert access_token == "token-value"
|
|
391
|
-
return {"seven_day": {"utilization": 37.5}}
|
|
392
|
-
|
|
393
|
-
def extract_usage_windows(
|
|
394
|
-
self, usage_payload: dict[str, object]
|
|
395
|
-
) -> _FakeWindows:
|
|
396
|
-
assert usage_payload == {"seven_day": {"utilization": 37.5}}
|
|
397
|
-
return _FakeWindows()
|
|
398
|
-
|
|
399
|
-
monkeypatch.setattr(
|
|
400
|
-
usage, "_load_resolve_usage_window_module", lambda: _FakeResolver()
|
|
401
|
-
)
|
|
402
|
-
weekly_utilization = usage._probe_weekly_utilization(
|
|
403
|
-
Path(PLACEHOLDER_CREDENTIALS_PRIMARY)
|
|
404
|
-
)
|
|
405
|
-
assert weekly_utilization == pytest.approx(37.5)
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
def should_read_both_meters_and_their_resets_for_one_account(
|
|
409
|
-
monkeypatch: pytest.MonkeyPatch,
|
|
410
|
-
) -> None:
|
|
411
|
-
session_reset = datetime(2026, 9, 22, 23, 0).astimezone()
|
|
412
|
-
weekly_reset = datetime(2026, 9, 26, 9, 0).astimezone()
|
|
413
|
-
|
|
414
|
-
class _FakeWindows:
|
|
21
|
+
class Windows:
|
|
415
22
|
session_utilization = 12.0
|
|
416
23
|
session_resets_at = session_reset
|
|
417
24
|
weekly_utilization = 64.0
|
|
418
25
|
weekly_resets_at = weekly_reset
|
|
419
26
|
|
|
420
|
-
class
|
|
421
|
-
def read_oauth_access_token(self, credentials_path: Path, now:
|
|
422
|
-
|
|
27
|
+
class Resolver:
|
|
28
|
+
def read_oauth_access_token(self, credentials_path: Path, now: datetime) -> str:
|
|
29
|
+
assert credentials_path == Path("account.json")
|
|
30
|
+
return "fixture-token"
|
|
423
31
|
|
|
424
32
|
def _fetch_usage_payload(self, access_token: str) -> dict[str, object]:
|
|
425
|
-
|
|
33
|
+
assert access_token == "fixture-token"
|
|
34
|
+
return {"usage": 64.0}
|
|
426
35
|
|
|
427
|
-
def extract_usage_windows(
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
return _FakeWindows()
|
|
36
|
+
def extract_usage_windows(self, payload: dict[str, object]) -> Windows:
|
|
37
|
+
assert payload == {"usage": 64.0}
|
|
38
|
+
return Windows()
|
|
431
39
|
|
|
432
|
-
monkeypatch.setattr(
|
|
433
|
-
|
|
434
|
-
)
|
|
435
|
-
|
|
436
|
-
assert account_meters == usage.AccountUsageMeters(
|
|
437
|
-
session_utilization=12.0,
|
|
438
|
-
session_resets_at=session_reset,
|
|
439
|
-
weekly_utilization=64.0,
|
|
440
|
-
weekly_resets_at=weekly_reset,
|
|
40
|
+
monkeypatch.setattr(usage, "_load_resolve_usage_window_module", lambda: Resolver())
|
|
41
|
+
|
|
42
|
+
assert usage.probe_account_meters(Path("account.json")) == usage.AccountUsageMeters(
|
|
43
|
+
12.0, session_reset, 64.0, weekly_reset
|
|
441
44
|
)
|
|
442
45
|
|
|
443
46
|
|
|
444
|
-
def
|
|
445
|
-
|
|
446
|
-
) -> None:
|
|
447
|
-
class _FakeResolver:
|
|
448
|
-
def read_oauth_access_token(
|
|
449
|
-
self, credentials_path: Path, now: object
|
|
450
|
-
) -> None:
|
|
47
|
+
def test_probe_rejects_missing_access_token(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
48
|
+
class Resolver:
|
|
49
|
+
def read_oauth_access_token(self, credentials_path: Path, now: datetime) -> None:
|
|
451
50
|
return None
|
|
452
51
|
|
|
453
|
-
def resolve_access_token(self, credentials_path: Path, now: object) -> str:
|
|
454
|
-
return "ingress-token-must-not-be-used"
|
|
455
|
-
|
|
456
52
|
def _fetch_usage_payload(self, access_token: str) -> dict[str, object]:
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
monkeypatch.setattr(
|
|
460
|
-
usage, "_load_resolve_usage_window_module", lambda: _FakeResolver()
|
|
461
|
-
)
|
|
462
|
-
with pytest.raises(usage.WeeklyUtilizationProbeError):
|
|
463
|
-
usage._probe_weekly_utilization(Path(PLACEHOLDER_CREDENTIALS_PRIMARY))
|
|
53
|
+
pytest.fail("meter request must not run")
|
|
464
54
|
|
|
55
|
+
monkeypatch.setattr(usage, "_load_resolve_usage_window_module", lambda: Resolver())
|
|
465
56
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
) -> None:
|
|
469
|
-
config_file = _write_chain_config(
|
|
470
|
-
tmp_path,
|
|
471
|
-
[_entry("claude-profile-c", credentials_path=PLACEHOLDER_CREDENTIALS_SECONDARY)],
|
|
472
|
-
)
|
|
57
|
+
with pytest.raises(usage.WeeklyUtilizationProbeError, match="bearer token"):
|
|
58
|
+
usage.probe_account_meters(Path("account.json"))
|
|
473
59
|
|
|
474
|
-
class _FakeResolver:
|
|
475
|
-
def read_oauth_access_token(
|
|
476
|
-
self, credentials_path: Path, now: object
|
|
477
|
-
) -> None:
|
|
478
|
-
return None
|
|
479
60
|
|
|
480
|
-
|
|
481
|
-
|
|
61
|
+
def test_probe_wraps_network_failure(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
62
|
+
class Resolver:
|
|
63
|
+
def read_oauth_access_token(self, credentials_path: Path, now: datetime) -> str:
|
|
64
|
+
return "fixture-token"
|
|
482
65
|
|
|
483
66
|
def _fetch_usage_payload(self, access_token: str) -> dict[str, object]:
|
|
484
|
-
raise
|
|
67
|
+
raise urllib.error.URLError("unavailable")
|
|
485
68
|
|
|
486
|
-
monkeypatch.setattr(
|
|
487
|
-
usage, "_load_resolve_usage_window_module", lambda: _FakeResolver()
|
|
488
|
-
)
|
|
489
|
-
monkeypatch.setattr(usage, "weekly_utilization_probe", usage._probe_weekly_utilization)
|
|
490
|
-
all_reports = usage.report_chain_weekly_usage(config_path=config_file)
|
|
491
|
-
assert all_reports[0].weekly_remaining_percent is None
|
|
492
|
-
assert all_reports[0].error is not None
|
|
493
|
-
assert "bearer token" in all_reports[0].error.lower()
|
|
69
|
+
monkeypatch.setattr(usage, "_load_resolve_usage_window_module", lambda: Resolver())
|
|
494
70
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
tmp_path: Path,
|
|
498
|
-
monkeypatch: pytest.MonkeyPatch,
|
|
499
|
-
capsys: pytest.CaptureFixture[str],
|
|
500
|
-
) -> None:
|
|
501
|
-
config_file = _write_chain_config(
|
|
502
|
-
tmp_path,
|
|
503
|
-
[
|
|
504
|
-
_entry("claude", credentials_path=PLACEHOLDER_CREDENTIALS_PRIMARY),
|
|
505
|
-
_entry("claude-profile-c", credentials_path=PLACEHOLDER_CREDENTIALS_SECONDARY),
|
|
506
|
-
],
|
|
507
|
-
)
|
|
508
|
-
|
|
509
|
-
def active_probe(credentials_path: Path) -> float:
|
|
510
|
-
path_key = credentials_path.as_posix()
|
|
511
|
-
if path_key == PLACEHOLDER_CREDENTIALS_PRIMARY:
|
|
512
|
-
raise ImportError("usage probe module failed to import")
|
|
513
|
-
return 25.0
|
|
514
|
-
|
|
515
|
-
monkeypatch.setattr(usage, "weekly_utilization_probe", active_probe)
|
|
516
|
-
exit_code = usage.main([CLI_CONFIG_PATH_FLAG, str(config_file)])
|
|
517
|
-
captured = capsys.readouterr()
|
|
518
|
-
assert exit_code == 0
|
|
519
|
-
payload = json.loads(captured.out)
|
|
520
|
-
assert payload[JSON_ACCOUNTS_KEY][0][JSON_WEEKLY_REMAINING_PERCENT_KEY] is None
|
|
521
|
-
assert "failed to import" in payload[JSON_ACCOUNTS_KEY][0][JSON_ERROR_KEY]
|
|
522
|
-
assert payload[JSON_ACCOUNTS_KEY][1][JSON_WEEKLY_REMAINING_PERCENT_KEY] == pytest.approx(
|
|
523
|
-
FULL_WEEKLY_PERCENT - 25.0
|
|
524
|
-
)
|
|
71
|
+
with pytest.raises(usage.WeeklyUtilizationProbeError, match="unavailable"):
|
|
72
|
+
usage.probe_account_meters(Path("account.json"))
|
|
525
73
|
|
|
526
74
|
|
|
527
|
-
def
|
|
75
|
+
def test_failed_module_load_does_not_poison_cache(
|
|
528
76
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
529
77
|
) -> None:
|
|
530
|
-
|
|
531
|
-
del sys.modules[RESOLVE_USAGE_WINDOW_MODULE_NAME]
|
|
78
|
+
monkeypatch.delitem(sys.modules, RESOLVE_USAGE_WINDOW_MODULE_NAME, raising=False)
|
|
532
79
|
(tmp_path / "resolve_usage_window.py").write_text(
|
|
533
80
|
"raise ImportError('intentional load failure')\n", encoding="utf-8"
|
|
534
81
|
)
|
|
535
82
|
monkeypatch.setattr(usage, "_usage_pause_scripts_directory", lambda: tmp_path)
|
|
83
|
+
|
|
536
84
|
with pytest.raises(ImportError, match="intentional load failure"):
|
|
537
85
|
usage._load_resolve_usage_window_module()
|
|
538
|
-
assert RESOLVE_USAGE_WINDOW_MODULE_NAME not in sys.modules
|
|
539
86
|
|
|
87
|
+
assert RESOLVE_USAGE_WINDOW_MODULE_NAME not in sys.modules
|
|
540
88
|
|
|
541
|
-
def test_load_resolve_usage_window_module_loads_real_probe_api() -> None:
|
|
542
|
-
if RESOLVE_USAGE_WINDOW_MODULE_NAME in sys.modules:
|
|
543
|
-
del sys.modules[RESOLVE_USAGE_WINDOW_MODULE_NAME]
|
|
544
|
-
loaded_module = usage._load_resolve_usage_window_module()
|
|
545
|
-
assert callable(loaded_module.read_oauth_access_token)
|
|
546
|
-
assert callable(loaded_module.resolve_access_token)
|
|
547
|
-
assert callable(loaded_module.extract_usage_windows)
|
|
548
|
-
assert callable(loaded_module._fetch_usage_payload)
|
|
549
|
-
assert sys.modules[RESOLVE_USAGE_WINDOW_MODULE_NAME] is loaded_module
|
|
550
89
|
|
|
90
|
+
def test_loader_reuses_cached_probe_module() -> None:
|
|
91
|
+
first = usage._load_resolve_usage_window_module()
|
|
551
92
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
assert first_load is second_load
|
|
93
|
+
assert callable(first.read_oauth_access_token)
|
|
94
|
+
assert callable(first.extract_usage_windows)
|
|
95
|
+
assert usage._load_resolve_usage_window_module() is first
|
|
556
96
|
|
|
557
97
|
|
|
558
|
-
def
|
|
559
|
-
scripts_directory = str(
|
|
98
|
+
def test_usage_module_imports_without_picker() -> None:
|
|
99
|
+
scripts_directory = str(Path(__file__).resolve().parent)
|
|
560
100
|
import_probe = (
|
|
561
101
|
"import sys; "
|
|
562
102
|
f"sys.path.insert(0, {scripts_directory!r}); "
|
|
563
103
|
"import claude_chain_usage; "
|
|
564
|
-
"assert callable(claude_chain_usage.
|
|
565
|
-
"assert callable(claude_chain_usage.rank_accounts_by_weekly_remaining)"
|
|
104
|
+
"assert callable(claude_chain_usage.probe_account_meters)"
|
|
566
105
|
)
|
|
567
106
|
completed = subprocess.run(
|
|
568
107
|
[sys.executable, "-S", "-E", "-c", import_probe],
|
|
@@ -571,4 +110,5 @@ def test_usage_module_imports_without_preloading_runner() -> None:
|
|
|
571
110
|
timeout=60,
|
|
572
111
|
check=False,
|
|
573
112
|
)
|
|
113
|
+
|
|
574
114
|
assert completed.returncode == 0, completed.stderr
|
|
@@ -11,7 +11,7 @@ from pathlib import Path
|
|
|
11
11
|
import pytest
|
|
12
12
|
|
|
13
13
|
_DISPATCHER_NAMES = ("resolve_worker_spawn.py", "invoke_code_review.py")
|
|
14
|
-
_SHARED_DIRECTORY_NAMES = ("advisor", "process-tree")
|
|
14
|
+
_SHARED_DIRECTORY_NAMES = ("advisor", "process-tree", "pr-loop")
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def _stage_profile_tree(
|
|
@@ -46,8 +46,8 @@ def _stage_shadow_import_root(
|
|
|
46
46
|
"raise RuntimeError('shadow tier_model_ids imported')\n",
|
|
47
47
|
encoding="utf-8",
|
|
48
48
|
)
|
|
49
|
-
(shadow_import_root / "
|
|
50
|
-
"raise RuntimeError('shadow
|
|
49
|
+
(shadow_import_root / "account_broker.py").write_text(
|
|
50
|
+
"raise RuntimeError('shadow account_broker imported')\n",
|
|
51
51
|
encoding="utf-8",
|
|
52
52
|
)
|
|
53
53
|
shadow_constants_root = shadow_import_root / "advisor_scripts_constants"
|
|
@@ -119,17 +119,13 @@ def test_imported_dispatcher_promotes_all_profile_owned_roots(
|
|
|
119
119
|
)
|
|
120
120
|
process_tree_config_root = process_tree_scripts_root / "config"
|
|
121
121
|
all_expected_roots = [
|
|
122
|
+
str(process_tree_config_root),
|
|
123
|
+
str(process_tree_scripts_root),
|
|
122
124
|
str(advisor_config_root),
|
|
123
125
|
str(advisor_scripts_root),
|
|
124
126
|
str(installed_dispatcher_scripts),
|
|
125
127
|
str(shadow_import_root),
|
|
126
128
|
]
|
|
127
|
-
if dispatcher_module_name == "resolve_worker_spawn":
|
|
128
|
-
all_expected_roots = [
|
|
129
|
-
str(process_tree_config_root),
|
|
130
|
-
str(process_tree_scripts_root),
|
|
131
|
-
*all_expected_roots,
|
|
132
|
-
]
|
|
133
129
|
child_code = "\n".join(
|
|
134
130
|
(
|
|
135
131
|
"import importlib",
|
|
@@ -7,8 +7,7 @@ from pathlib import Path
|
|
|
7
7
|
import pytest
|
|
8
8
|
|
|
9
9
|
import invoke_code_review as invoker
|
|
10
|
-
from
|
|
11
|
-
from dev_env_scripts_constants.claude_chain_constants import TERMINAL_STATUS_SERVED
|
|
10
|
+
from dev_env_scripts_constants.account_broker_constants import JobOutcome
|
|
12
11
|
from dev_env_scripts_constants.code_review_constants import (
|
|
13
12
|
ALL_FINDING_SEVERITIES,
|
|
14
13
|
ALL_LOOP_TERMINALS,
|
|
@@ -77,14 +76,16 @@ UNPRIVILEGED_USER_ID: int = 1000
|
|
|
77
76
|
|
|
78
77
|
def _serve_a_refusing_binary(
|
|
79
78
|
*_all_positional: object, **_all_keyword: object
|
|
80
|
-
) ->
|
|
81
|
-
return
|
|
82
|
-
served_command=SERVED_COMMAND_NAME,
|
|
79
|
+
) -> JobOutcome:
|
|
80
|
+
return JobOutcome(
|
|
83
81
|
returncode=REVIEW_FAILURE_RETURNCODE,
|
|
84
82
|
stdout=EMPTY_REVIEW_STDOUT,
|
|
85
83
|
stderr=REVIEW_BINARY_REFUSAL_TEXT,
|
|
84
|
+
account_name=SERVED_COMMAND_NAME,
|
|
86
85
|
attempts=(),
|
|
87
|
-
|
|
86
|
+
status="advisor_blocked",
|
|
87
|
+
session_id=None,
|
|
88
|
+
wait_reset_at=None,
|
|
88
89
|
)
|
|
89
90
|
|
|
90
91
|
|