claude-dev-env 8.37.3 → 8.38.0
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/docs/account-broker.md +32 -0
- package/package.json +1 -1
- package/scripts/account_broker.py +387 -0
- package/scripts/account_broker_support.py +377 -0
- package/scripts/dev_env_scripts_constants/account_broker_constants.py +301 -0
- package/scripts/test_account_broker.py +501 -0
- package/scripts/test_account_broker_guard.py +109 -0
- package/scripts/test_account_broker_support.py +107 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import subprocess
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from types import SimpleNamespace
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
import account_broker_support as support
|
|
12
|
+
from account_broker_support import Account, Meters, Product, ProductAdapter
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_should_load_claude_roster_from_both_lists(
|
|
16
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
17
|
+
) -> None:
|
|
18
|
+
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
|
19
|
+
monkeypatch.setattr(support, "default_profile_home", lambda name="extra": tmp_path / "profiles" / name)
|
|
20
|
+
main_home = tmp_path / ".claude"
|
|
21
|
+
main_home.mkdir()
|
|
22
|
+
(main_home / "claude-chain.json").write_text(
|
|
23
|
+
json.dumps({"chain": [{"command": "claude"}, {"command": "alternate", "credentials_path": str(tmp_path / "alternate" / ".credentials.json") }]}),
|
|
24
|
+
encoding="utf-8",
|
|
25
|
+
)
|
|
26
|
+
(main_home / "extra-profiles.json").write_text('["extra"]', encoding="utf-8")
|
|
27
|
+
|
|
28
|
+
accounts = support.load_claude_accounts()
|
|
29
|
+
|
|
30
|
+
assert [each_account.name for each_account in accounts] == ["main", "alternate", "extra"]
|
|
31
|
+
assert accounts[1].home == (tmp_path / "alternate").resolve()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_should_load_codex_roster_without_meter_reads(
|
|
35
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
36
|
+
) -> None:
|
|
37
|
+
monkeypatch.setattr(support.codex_account_choice, "default_profiles_root", lambda: tmp_path)
|
|
38
|
+
monkeypatch.setattr(support.codex_account_choice, "codex_account_names", lambda _: ("one",))
|
|
39
|
+
monkeypatch.delenv("CODEX_ACCOUNT_PROFILES", raising=False)
|
|
40
|
+
(tmp_path / "account-launchers.json").write_text('["one"]', encoding="utf-8")
|
|
41
|
+
|
|
42
|
+
assert [each_account.name for each_account in support.load_codex_accounts()] == ["one"]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_should_read_claude_meters_through_injected_probe(
|
|
46
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
47
|
+
) -> None:
|
|
48
|
+
usage = SimpleNamespace(
|
|
49
|
+
session_utilization=20.0,
|
|
50
|
+
session_resets_at=datetime(2026, 10, 3, tzinfo=timezone.utc),
|
|
51
|
+
weekly_utilization=30.0,
|
|
52
|
+
weekly_resets_at=None,
|
|
53
|
+
)
|
|
54
|
+
monkeypatch.setattr(support, "probe_account_meters", lambda _: usage)
|
|
55
|
+
|
|
56
|
+
meters = support.read_claude_meters(Account(Product.CLAUDE, "one", tmp_path))
|
|
57
|
+
|
|
58
|
+
assert meters.session_percent_left == 80.0
|
|
59
|
+
assert meters.weekly_percent_left == 70.0
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_should_read_codex_meters_through_injected_reader(
|
|
63
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
64
|
+
) -> None:
|
|
65
|
+
short = SimpleNamespace(duration_minutes=300, used_percent=20.0, resets_at=None)
|
|
66
|
+
weekly = SimpleNamespace(duration_minutes=10080, used_percent=30.0, resets_at=None)
|
|
67
|
+
usage = SimpleNamespace(all_windows=(short, weekly), short_window_percent_left=80.0)
|
|
68
|
+
monkeypatch.setattr(support.codex_account_meters, "resolve_codex_path", lambda _: tmp_path / "codex")
|
|
69
|
+
monkeypatch.setattr(support.codex_account_meters, "read_codex_meters", lambda *_: usage)
|
|
70
|
+
|
|
71
|
+
meters = support.read_codex_account_meters(Account(Product.CODEX, "one", tmp_path))
|
|
72
|
+
|
|
73
|
+
assert meters.session_percent_left == 80.0
|
|
74
|
+
assert meters.weekly_percent_left == 70.0
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_should_cache_account_reading_under_injected_state_path(
|
|
78
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
79
|
+
) -> None:
|
|
80
|
+
monkeypatch.setattr(support, "broker_state_path", lambda: tmp_path / "broker" / "state.json")
|
|
81
|
+
account = Account(Product.CODEX, "one", tmp_path / "one")
|
|
82
|
+
seen: list[str] = []
|
|
83
|
+
|
|
84
|
+
def reader(selected: Account) -> Meters:
|
|
85
|
+
seen.append(selected.name)
|
|
86
|
+
return Meters(80.0, None, 80.0, None)
|
|
87
|
+
|
|
88
|
+
adapter = ProductAdapter(lambda: (account,), reader, "CODEX_HOME", (), False)
|
|
89
|
+
state = support._load_state(support.broker_state_path())
|
|
90
|
+
now = datetime(2026, 10, 3, tzinfo=timezone.utc)
|
|
91
|
+
|
|
92
|
+
assert support.read_accounts(Product.CODEX, adapter, all_state=state, now=now)[0].meters is not None
|
|
93
|
+
assert support.read_accounts(Product.CODEX, adapter, all_state=state, now=now)[0].meters is not None
|
|
94
|
+
assert seen == ["one"]
|
|
95
|
+
assert support.broker_state_path().is_file()
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_should_extract_session_id_and_restore_runner() -> None:
|
|
99
|
+
original = support.subprocess_runner
|
|
100
|
+
|
|
101
|
+
def runner(argv: object, **options: object) -> subprocess.CompletedProcess[str]:
|
|
102
|
+
return subprocess.CompletedProcess(argv, 0, "", "")
|
|
103
|
+
|
|
104
|
+
with support.override_subprocess_runner(runner):
|
|
105
|
+
assert support.subprocess_runner is runner
|
|
106
|
+
assert support.subprocess_runner is original
|
|
107
|
+
assert support.extract_session_id_from_stdout('{"session_id":"one"}') == "one"
|