Files
clawdmeter/daemon/tests/test_providers.py
T
wenil 83bd2bed43 v3 M1b: runtime per-provider theming (firmware) + accent push (daemon)
The watch recolors to the active provider's brand, live, and it's cheap to add
more providers later.

Firmware:
- theme.h: expose THEME_ACCENT_HEX; ui.cpp: COL_ACCENT becomes a runtime
  lv_color_t backed by a shared style (s_accent_style). Static accent widgets
  (launcher tile icons, usage status line, session tokens) attach the shared
  style, so ui_set_theme() recolors them instantly via lv_obj_report_style_change
  — no widget tracking, no recreation.
- ui_set_theme(pv, accent_rgb): swaps the accent + a small logo table keyed by
  provider id (fallback to the Claude mark until a provider ships a logo). Change-
  guarded, so the ~3s payload cadence doesn't churn.
- main.cpp: parse "pv" (id) + "ac" (0xRRGGBB) from the payload and apply.
- Adding a provider needs zero firmware color edits (accent is data-driven) and
  at most one logo-table row.

Daemon: push "ac" = active provider's brand accent alongside "pv".

Test fix: test_providers used asyncio.run(), which closed the suite's shared
event loop and broke every later get_event_loop() test (Py3.13) — mirror the
repo's _run() helper instead.

Verified on the 2.06: forcing an OpenAI-green theme recolored the live launcher
icons (report_style_change path). 216 builds clean; daemon suite 94 passed.
2026-07-10 00:40:24 +03:00

87 lines
3.4 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, 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"
stub = get_provider("openai")
assert isinstance(stub, StubProvider)
assert stub.id == "openai" and stub.accent == "10a37f"
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