108 lines
4.1 KiB
Python
108 lines
4.1 KiB
Python
"""Service-system hand-off: status mapping, note, and the #kota-report fragment
|
|
(decoded exactly as the v1.1.0 service page does: base64url + UTF-8 JSON)."""
|
|
|
|
import base64
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from kota_tech.core.logfile import read_log_file
|
|
from kota_tech.core.logline import parse_line
|
|
from kota_tech.diag.engine import DiagnosticsEngine
|
|
from kota_tech.service.report import (
|
|
CONNECTIVITY, HARDWARE, OK, decode_fragment, normalize_mac, report_url, service_note, service_status,
|
|
)
|
|
|
|
FIX = Path(__file__).parent / "fixtures"
|
|
|
|
|
|
def bench():
|
|
e = DiagnosticsEngine()
|
|
for ln in read_log_file(FIX / "live_boot_A4CB8FC946F8_fw1.0.txt"):
|
|
e.feed(ln)
|
|
return e
|
|
|
|
|
|
def test_engine_reads_mac_from_boot_log():
|
|
assert bench().mac == "A4:CB:8F:C9:46:F8"
|
|
|
|
|
|
def test_bench_box_is_a_connectivity_problem():
|
|
e = bench()
|
|
assert service_status(e.report()) == CONNECTIVITY
|
|
note = service_note(e.report())
|
|
assert note.startswith("[KOTA Technician Tool")
|
|
assert "not commissioned in Matter" in note and "↳ [Error] No Internet" in note
|
|
assert "Firmware: v1.0" in note
|
|
|
|
|
|
def test_hardware_and_healthy_statuses():
|
|
e = DiagnosticsEngine()
|
|
for ln in ["ESP-ROM:esp32s3-20210327", "I (1299) app_init: Project name: kota-sensorboard",
|
|
"E (3511) MAIN: Failed to initialize RTC: ESP_ERR_TIMEOUT"]:
|
|
e.feed(parse_line(ln))
|
|
assert service_status(e.report()) == HARDWARE
|
|
ok = DiagnosticsEngine()
|
|
for ln in ["ESP-ROM:esp32s3-20210327", "I (1299) app_init: Project name: kota-sensorboard",
|
|
"I (31000) INFLUX: HTTP request performed, status code: 204"]:
|
|
ok.feed(parse_line(ln))
|
|
assert service_status(ok.report()) == OK
|
|
|
|
|
|
def test_url_shape_and_payload_roundtrip():
|
|
e = bench()
|
|
url = report_url(e.mac, e.report(), "https://service.kota-app.com")
|
|
assert url.startswith("https://service.kota-app.com/A4CB8FC946F8#kota-report=")
|
|
assert "?" not in url # nothing in the query: the fragment is never sent to a server
|
|
data = decode_fragment(url)
|
|
assert data["v"] == 1 and data["mac"] == "A4CB8FC946F8" and data["status"] == CONNECTIVITY
|
|
|
|
|
|
def test_fragment_decodes_like_the_service_page():
|
|
# Mirror of index.html applyToolPrefill: base64url -> pad -> bytes -> UTF-8 -> JSON
|
|
e = bench()
|
|
e.note_technician("Zażółć gęślą jaźń -> Purple") # non-ASCII survives
|
|
url = report_url("a4:cb:8f:c9:46:f8", e.report())
|
|
frag = url.split("#kota-report=")[1]
|
|
assert set(frag) <= set("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_") # page regex
|
|
padded = frag.replace("-", "+").replace("_", "/")
|
|
padded += "==="[(len(padded) + 3) % 4:]
|
|
data = json.loads(base64.b64decode(padded).decode("utf-8"))
|
|
assert "Zażółć gęślą jaźń" in data["note"]
|
|
|
|
|
|
def test_note_is_limited_and_no_secrets():
|
|
e = bench()
|
|
for i in range(400):
|
|
e.note_technician(f"check {i} " + "x" * 50)
|
|
note = service_note(e.report(), limit=4000)
|
|
assert len(note) <= 4000
|
|
assert "INFLUX_TOKEN" not in note
|
|
|
|
|
|
@pytest.mark.parametrize("bad", ["", "A4CB8F", "zz:cb:8f:c9:46:f8"])
|
|
def test_invalid_mac_rejected(bad):
|
|
with pytest.raises(ValueError):
|
|
report_url(bad, bench().report())
|
|
|
|
|
|
def test_normalize_mac():
|
|
assert normalize_mac("a4:cb:8f:c9:46:f8") == "A4CB8FC946F8"
|
|
|
|
|
|
def test_note_mirrors_the_diagnosis_panel():
|
|
from pathlib import Path
|
|
from kota_tech.core.logfile import read_log_file
|
|
e = DiagnosticsEngine()
|
|
for ln in read_log_file(Path(__file__).parent / "fixtures" / "live_boot_A4CB8FC946F8_fw1.0.16_uncommissioned.txt"):
|
|
e.feed(ln)
|
|
note = service_note(e.report())
|
|
assert len(note) <= 5000
|
|
for part in ("Status LED should be: Cyan, breathing", "Box self-report (KOTA lines):",
|
|
"✔ I2C: found: AS7341, DS1307, SEN66, BME688", "Start-up progress (current boot):",
|
|
"✔ SD card mounted (1.7 s)", "○ Connected to Wi-Fi", "What to do:",
|
|
"Seen 12x, first at 21.6 s after start, last at 126.8 s",
|
|
"↳ [Error] No Internet: server names cannot be resolved"):
|
|
assert part in note, part
|