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.
@@ -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"