Files
Bolt-Pet/tests/test_doctor.py
T

115 lines
4.3 KiB
Python

"""The preflight. Its one job is to never be the thing that's broken.
A doctor that raises on a broken install diagnoses the wrong patient, so the
tests that matter here are the ugly-input ones: no config at all, a check that
throws, a dependency missing. The individual diagnoses are simple enough to
read; that they *run* on a machine missing everything is the property worth
pinning down.
"""
import sys
from pathlib import Path
import pytest
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from bolt_pet import config, doctor
from bolt_pet.doctor import FAIL, OK, WARN
def test_every_check_returns_a_verdict_on_a_bare_machine(monkeypatch):
"""Nothing configured, nothing installed — still a full report."""
for name in ("SERVER_URL", "API_KEY", "ELEVENLABS_API_KEY", "ELEVENLABS_VOICE_ID"):
monkeypatch.setattr(config, name, "")
monkeypatch.setattr(doctor, "_module", lambda _n: False)
results = doctor.run()
assert len(results) == len(doctor.CHECKS)
assert all(c.status in (OK, WARN, FAIL) for c in results)
assert all(c.name and c.detail for c in results)
def test_a_check_that_raises_does_not_hide_the_others():
"""One broken probe must not cost you the other eleven diagnoses."""
def explode(*_args):
raise RuntimeError("boom")
original = doctor.CHECKS
doctor.CHECKS = (("mic", explode),) + original[:2]
try:
results = doctor.run()
finally:
doctor.CHECKS = original
assert len(results) == 3
assert results[0].status == FAIL
assert "boom" in results[0].detail
def test_missing_server_config_is_a_failure_not_a_warning(monkeypatch):
"""Without it the controller exits its thread at startup — the pet looks
alive and simply never answers. That is the worst failure mode there is."""
monkeypatch.setattr(config, "missing_config", lambda: ["BOLT_SERVER_URL", "DESK_API_KEY"])
check = doctor.check_config()
assert check.status == FAIL
assert "BOLT_SERVER_URL" in check.detail
assert check.fix
def test_a_configured_server_passes_without_being_contacted(monkeypatch):
"""The shallow run must not need the network — it's the first thing you
reach for when the network is what's wrong."""
monkeypatch.setattr(config, "missing_config", lambda: [])
monkeypatch.setattr(config, "SERVER_URL", "http://bolt.local:8000")
assert doctor.check_config().status == OK
assert doctor.check_server(deep=False).status == OK
def test_every_problem_comes_with_something_to_do_about_it(monkeypatch):
""""screen reading: warn" is useless on its own; "apt install tesseract-ocr"
is the entire point of the tool."""
for name in ("SERVER_URL", "API_KEY"):
monkeypatch.setattr(config, name, "")
monkeypatch.setattr(doctor, "_module", lambda _n: False)
for check in doctor.run():
if check.status == FAIL:
assert check.fix, f"{check.name} says what's wrong but not what to do"
def test_the_exit_code_is_nonzero_only_for_real_failures(monkeypatch, capsys):
monkeypatch.setattr(doctor, "run", lambda deep=False: [
doctor.Check("a", OK, "fine"), doctor.Check("b", WARN, "degraded")])
assert doctor.main([]) == 0
monkeypatch.setattr(doctor, "run", lambda deep=False: [doctor.Check("a", FAIL, "broken")])
assert doctor.main([]) == 1
assert "Fix those first" in capsys.readouterr().out
def test_deep_is_off_unless_asked(monkeypatch):
seen = []
monkeypatch.setattr(doctor, "run", lambda deep=False: seen.append(deep) or [])
doctor.main([])
doctor.main(["--deep"])
assert seen == [False, True]
def test_a_slow_silence_timeout_is_flagged(monkeypatch):
"""The setting most likely to make it feel sluggish, and the least obvious
— it is pure dead air before anything at all starts happening."""
monkeypatch.setattr(config, "SILENCE_END_SEC", 2.0)
check = doctor.check_latency()
assert check.status == WARN
assert "2s" in check.detail or "2 " in check.detail
monkeypatch.setattr(config, "SILENCE_END_SEC", 0.9)
assert doctor.check_latency().status == OK
def test_a_check_line_renders_the_fix_only_when_there_is_a_problem():
assert "→" not in doctor.Check("x", OK, "all good", fix="unused").line()
assert "→ do the thing" in doctor.Check("x", WARN, "hmm", fix="do the thing").line()