"""v3 M1c — control-panel provider API: secret masking + live-switch sink. Provider api_keys (z.ai) get the same never-leave-the-process treatment as the HA token, and flipping active_provider notifies the injected command sink so a connected watch switches without waiting for the ~60s poll. """ import json import pytest from fastapi.testclient import TestClient from daemon import config, server @pytest.fixture def client(tmp_path, monkeypatch): cfg = { "version": 2, "providers": { "anthropic": {"enabled": True}, "openai": {"enabled": False}, "zai": {"enabled": False, "base_url": "https://api.z.ai", "api_key": "secret-key"}, }, "active_provider": "anthropic", "display_order": ["anthropic", "openai", "zai"], } p = tmp_path / "config.json" p.write_text(json.dumps(cfg), encoding="utf-8") monkeypatch.setenv("CLAWDMETER_CONFIG", str(p)) monkeypatch.setenv("LOCALAPPDATA", str(tmp_path)) monkeypatch.setattr(server, "_command_sink", None) return TestClient(server.app) def test_zai_api_key_masked_in_get(client): r = client.get("/api/config").json() assert r["providers"]["zai"]["api_key"] == server.MASK assert r["providers"]["zai"]["base_url"] == "https://api.z.ai" # non-secret shown def test_masked_api_key_preserved_on_put(client): # Echo the mask back unchanged => keep the stored secret; other fields apply. client.put("/api/config", json={"providers": {"zai": {"api_key": server.MASK, "enabled": True}}}) saved = config.load_config()["providers"]["zai"] assert saved["api_key"] == "secret-key" assert saved["enabled"] is True def test_real_api_key_stored_on_put(client): client.put("/api/config", json={"providers": {"zai": {"api_key": "new-key"}}}) assert config.load_config()["providers"]["zai"]["api_key"] == "new-key" def test_active_provider_switch_calls_sink(client, monkeypatch): calls = [] monkeypatch.setattr(server, "_command_sink", lambda pid: calls.append(pid)) client.put("/api/config", json={"active_provider": "openai"}) assert calls == ["openai"] assert config.active_provider_id() == "openai" def test_no_switch_when_active_unchanged(client, monkeypatch): calls = [] monkeypatch.setattr(server, "_command_sink", lambda pid: calls.append(pid)) client.put("/api/config", json={"active_provider": "anthropic"}) # already active assert calls == [] def test_unknown_provider_id_ignored_on_put(client): client.put("/api/config", json={"providers": {"bogus": {"enabled": True}}}) assert "bogus" not in config.load_config()["providers"]