|
| 1 | +"""Auto-start the Power trial on sign-in — but ONLY on the Power build. |
| 2 | +
|
| 3 | +Possessing the Power build means the user came through the gated download, so |
| 4 | +signing in is the opt-in. The server endpoint is idempotent and one-trial-per- |
| 5 | +account, so calling it on every sign-in is safe; these tests pin the CLIENT |
| 6 | +side: it fires for a Power build and never for an OSS build. |
| 7 | +""" |
| 8 | + |
| 9 | +from __future__ import annotations |
| 10 | + |
| 11 | +import json |
| 12 | +from types import SimpleNamespace |
| 13 | + |
| 14 | +import pytest |
| 15 | + |
| 16 | +from heard import url_scheme |
| 17 | + |
| 18 | + |
| 19 | +class _FakeResp: |
| 20 | + def __init__(self, payload): |
| 21 | + self._p = json.dumps(payload).encode() |
| 22 | + |
| 23 | + def read(self): |
| 24 | + return self._p |
| 25 | + |
| 26 | + def __enter__(self): |
| 27 | + return self |
| 28 | + |
| 29 | + def __exit__(self, *a): |
| 30 | + return False |
| 31 | + |
| 32 | + |
| 33 | +@pytest.fixture |
| 34 | +def cfg(monkeypatch): |
| 35 | + store = {"heard_api_base": "https://api.heard.dev"} |
| 36 | + monkeypatch.setattr(url_scheme.config, "load", lambda: dict(store)) |
| 37 | + monkeypatch.setattr(url_scheme.config, "set_value", |
| 38 | + lambda k, v: store.__setitem__(k, v)) |
| 39 | + return store |
| 40 | + |
| 41 | + |
| 42 | +def _capture_requests(monkeypatch, payload): |
| 43 | + calls = [] |
| 44 | + |
| 45 | + def fake_urlopen(req, *a, **kw): |
| 46 | + calls.append(req.full_url) |
| 47 | + return _FakeResp(payload) |
| 48 | + |
| 49 | + monkeypatch.setattr(url_scheme.urllib.request, "urlopen", fake_urlopen) |
| 50 | + return calls |
| 51 | + |
| 52 | + |
| 53 | +def test_oss_build_never_starts_a_trial(cfg, monkeypatch): |
| 54 | + cfg["voice_service_cmd"] = "" # OSS build |
| 55 | + calls = _capture_requests(monkeypatch, {"plan": "power"}) |
| 56 | + url_scheme._maybe_start_power_trial("tok") |
| 57 | + assert calls == [], "OSS build must not call the Power trial endpoint" |
| 58 | + assert cfg.get("heard_plan") is None |
| 59 | + |
| 60 | + |
| 61 | +def test_power_build_starts_the_trial_and_persists_plan(cfg, monkeypatch): |
| 62 | + cfg["voice_service_cmd"] = "python -m heard_power serve" # Power build |
| 63 | + calls = _capture_requests( |
| 64 | + monkeypatch, {"plan": "power", "trial_expires_at": 1234567890} |
| 65 | + ) |
| 66 | + url_scheme._maybe_start_power_trial("tok") |
| 67 | + assert any("/v1/power/trial/start" in u for u in calls) |
| 68 | + assert cfg["heard_plan"] == "power" |
| 69 | + assert cfg["heard_trial_expires_at"] == 1234567890 |
| 70 | + |
| 71 | + |
| 72 | +def test_trial_already_used_does_not_flip_local_plan(cfg, monkeypatch): |
| 73 | + """Server refuses (trial_used) → we must not claim Power locally.""" |
| 74 | + cfg["voice_service_cmd"] = "python -m heard_power serve" |
| 75 | + _capture_requests(monkeypatch, {"ok": False, "reason": "trial_used", "plan": "expired"}) |
| 76 | + url_scheme._maybe_start_power_trial("tok") |
| 77 | + assert cfg.get("heard_plan") != "power" |
| 78 | + |
| 79 | + |
| 80 | +def test_network_failure_is_swallowed(cfg, monkeypatch): |
| 81 | + cfg["voice_service_cmd"] = "python -m heard_power serve" |
| 82 | + |
| 83 | + def boom(*a, **kw): |
| 84 | + raise OSError("offline") |
| 85 | + |
| 86 | + monkeypatch.setattr(url_scheme.urllib.request, "urlopen", boom) |
| 87 | + url_scheme._maybe_start_power_trial("tok") # must not raise |
| 88 | + assert cfg.get("heard_plan") != "power" |
| 89 | + |
| 90 | + |
| 91 | +def test_apply_token_invokes_autostart(cfg, monkeypatch): |
| 92 | + """The sign-in path must actually call the autostart hook.""" |
| 93 | + cfg["voice_service_cmd"] = "python -m heard_power serve" |
| 94 | + seen = {} |
| 95 | + monkeypatch.setattr(url_scheme, "_maybe_start_power_trial", |
| 96 | + lambda tok: seen.setdefault("tok", tok)) |
| 97 | + monkeypatch.setattr(url_scheme, "_refresh_byok_enabled", lambda tok: None) |
| 98 | + monkeypatch.setattr(url_scheme, "_reload_and_selftest", lambda: None) |
| 99 | + monkeypatch.setattr(url_scheme, "_bring_onboarding_forward_signed_in", lambda e: None) |
| 100 | + |
| 101 | + url_scheme._apply_token("tok123", "trial", "", 0) |
| 102 | + assert seen.get("tok") == "tok123", "sign-in did not auto-start the Power trial" |
0 commit comments