Files
clawdmeter/daemon/tests/test_providers.py
T
wenilandClaude Opus 4.8 b63a6a62ae v3 M2: OpenAI Codex provider — real rate-limit % from local Codex rollouts
OpenAICodexProvider replaces the openai stub. It reads the rate-limit snapshot
the Codex CLI already records locally (no second auth): each turn Codex writes a
token_count event into $CODEX_HOME/sessions/**/rollout-*.jsonl whose
payload.rate_limits mirrors Claude's model — primary (5h) + secondary (weekly),
each with used_percent + resets_at + plan_type. The provider surfaces the
freshest such snapshot; a window whose reset time has passed is reported as a
fresh 0% (local read, so current in-session and self-heals between sessions).

primary -> s/sr, secondary -> w/wr, plan_type -> status ("Plus"/…),
rate_limit_reached_type -> "limited". No per-token cost (subscription), so the
two rate-limit bars are the metric, per the locked v3 decision.

Verified against the real ~/.codex: full daemon payload with openai active =
{pv:openai, pnm:"OpenAI Codex", ac:0x10a37f, st:"Plus", ok:true, weekly reset}.
registry test updated (openai now real, zai still stub); +7 provider tests.
113 passed, 2 Linux-only failures (baseline). spec: hiddenimport added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 07:14:15 +03:00

91 lines
3.6 KiB
Python

"""v3 provider layer + config v2 tests.
Covers the framework M1 lands: config v1->v2 migration keeps Anthropic behaviour,
the provider registry hands out the right implementations, and ProviderStatus
maps to the compact BLE fields the firmware parser expects.
"""
import asyncio
import json
from daemon import config
from daemon.providers import (
get_provider, AnthropicProvider, OpenAICodexProvider, StubProvider, ProviderStatus)
def _run(coro):
# Reuse the suite's shared event loop (never closed) like the other test
# modules — asyncio.run() would close it and set the current loop to None,
# breaking every later test that calls get_event_loop() (Python 3.13).
return asyncio.get_event_loop().run_until_complete(coro)
def _write(tmp_path, monkeypatch, data: dict):
"""Point the config module at an isolated temp config (and LOCALAPPDATA, so
the legacy-ha_config migration can't reach the real machine's file)."""
p = tmp_path / "config.json"
p.write_text(json.dumps(data), encoding="utf-8")
monkeypatch.setenv("CLAWDMETER_CONFIG", str(p))
monkeypatch.setenv("LOCALAPPDATA", str(tmp_path))
return p
def test_v1_config_migrates_to_provider_defaults(tmp_path, monkeypatch):
# A v1 config has no providers/active_provider; load_config fills them so
# behaviour is unchanged — Anthropic enabled and active.
_write(tmp_path, monkeypatch, {
"version": 1, "ha": {"url": "", "token": "", "entities": []},
"buttons": [], "settings": {}})
cfg = config.load_config()
assert cfg["active_provider"] == "anthropic"
assert cfg["providers"]["anthropic"]["enabled"] is True
assert config.enabled_providers(cfg) == ["anthropic"]
def test_unknown_active_provider_falls_back(tmp_path, monkeypatch):
_write(tmp_path, monkeypatch, {"version": 2, "active_provider": "bogus", "providers": {}})
cfg = config.load_config()
assert cfg["active_provider"] == "anthropic"
assert config.active_provider_id(cfg) == "anthropic"
def test_display_order_covers_all_and_filters_unknown(tmp_path, monkeypatch):
_write(tmp_path, monkeypatch, {"version": 2, "display_order": ["zai", "bogus", "openai"]})
cfg = config.load_config()
# "bogus" dropped; the missing "anthropic" appended at the end.
assert cfg["display_order"] == ["zai", "openai", "anthropic"]
def test_enabled_providers_respects_order_and_flag(tmp_path, monkeypatch):
_write(tmp_path, monkeypatch, {
"version": 2, "display_order": ["zai", "openai", "anthropic"],
"providers": {"anthropic": {"enabled": True},
"openai": {"enabled": True},
"zai": {"enabled": False}}})
cfg = config.load_config()
assert config.enabled_providers(cfg) == ["openai", "anthropic"]
def test_registry_types():
a = get_provider("anthropic")
assert isinstance(a, AnthropicProvider) and a.id == "anthropic"
o = get_provider("openai")
assert isinstance(o, OpenAICodexProvider)
assert o.id == "openai" and o.accent == "10a37f"
stub = get_provider("zai") # z.ai is still a stub until M3
assert isinstance(stub, StubProvider)
assert stub.id == "zai" and stub.accent == "3859ff"
def test_stub_provider_poll_is_safe():
st = _run(get_provider("zai").poll())
assert isinstance(st, ProviderStatus)
assert st.ok is False
def test_provider_status_payload_keys():
st = ProviderStatus(s=50, w=10, ok=True, tokens_today=5, cost_cents_today=3)
p = st.to_payload()
assert set(p) == {"s", "sr", "w", "wr", "st", "ok", "tk", "to", "tc", "tn"}
assert p["s"] == 50 and p["tk"] == 5 and p["tc"] == 3 and p["ok"] is True