Add HTTP API contract fixtures
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
FIXTURES = ROOT / "tests" / "fixtures" / "http_api"
|
||||
FIRMWARE = ROOT / "firmware" / "esp32" / "overland-controller" / "overland-controller.ino"
|
||||
|
||||
|
||||
def load_fixture(name):
|
||||
return json.loads((FIXTURES / name).read_text())
|
||||
|
||||
|
||||
def firmware_source():
|
||||
return FIRMWARE.read_text()
|
||||
|
||||
|
||||
def assert_keys(payload, keys):
|
||||
missing = set(keys) - set(payload)
|
||||
assert not missing, f"missing keys: {sorted(missing)}"
|
||||
|
||||
|
||||
def registered_routes():
|
||||
pattern = re.compile(r'server\.on\("([^"]+)"(?:,\s*(HTTP_[A-Z]+))?')
|
||||
return {(match.group(1), match.group(2) or "ANY") for match in pattern.finditer(firmware_source())}
|
||||
|
||||
|
||||
def test_firmware_registers_current_http_contract_routes():
|
||||
routes = registered_routes()
|
||||
|
||||
expected_routes = {
|
||||
("/status", "ANY"),
|
||||
("/config", "HTTP_GET"),
|
||||
("/relay/set", "HTTP_POST"),
|
||||
("/config/wifi", "HTTP_GET"),
|
||||
("/config/wifi", "HTTP_POST"),
|
||||
("/wifi/connect", "HTTP_POST"),
|
||||
("/wifi/clear", "HTTP_POST"),
|
||||
("/temps/scan", "HTTP_POST"),
|
||||
("/temps/assign", "HTTP_POST"),
|
||||
("/temps/clear", "HTTP_POST"),
|
||||
}
|
||||
|
||||
assert expected_routes <= routes
|
||||
|
||||
|
||||
def test_status_fixture_matches_dashboard_contract_shape():
|
||||
payload = load_fixture("status_response.json")
|
||||
|
||||
assert_keys(payload, [
|
||||
"type",
|
||||
"timestamp",
|
||||
"battery",
|
||||
"temps",
|
||||
"relays",
|
||||
"vehicle",
|
||||
"network",
|
||||
"alarms",
|
||||
"system",
|
||||
"config",
|
||||
])
|
||||
assert payload["type"] == "status_response"
|
||||
|
||||
assert_keys(payload["battery"], [
|
||||
"source",
|
||||
"connected",
|
||||
"soc",
|
||||
"voltage",
|
||||
"current",
|
||||
"remaining_ah",
|
||||
"capacity_ah",
|
||||
"runtime_hours",
|
||||
"temperature_f",
|
||||
"cycle_count",
|
||||
"cell_count",
|
||||
"ntc_count",
|
||||
"cell_voltages",
|
||||
"cell_min_voltage",
|
||||
"cell_max_voltage",
|
||||
"cell_delta_mv",
|
||||
"cells_valid",
|
||||
])
|
||||
assert_keys(payload["temps"][0], [
|
||||
"id",
|
||||
"name",
|
||||
"enabled",
|
||||
"weather",
|
||||
"online",
|
||||
"temperature_f",
|
||||
])
|
||||
assert_keys(payload["relays"][0], ["id", "name", "pin", "enabled", "state"])
|
||||
assert_keys(payload["network"], [
|
||||
"wifi_enabled",
|
||||
"uart_connected",
|
||||
"ap_enabled",
|
||||
"ap_ip",
|
||||
"sta_enabled",
|
||||
"sta_connected",
|
||||
"sta_ssid",
|
||||
"sta_ip",
|
||||
"saved_network_count",
|
||||
"saved_networks",
|
||||
])
|
||||
assert_keys(payload["system"], [
|
||||
"firmware_name",
|
||||
"firmware_version",
|
||||
"build_date",
|
||||
"build_time",
|
||||
"uptime_seconds",
|
||||
])
|
||||
|
||||
|
||||
def test_config_fixture_matches_current_config_response_shape():
|
||||
payload = load_fixture("config_response.json")
|
||||
|
||||
assert_keys(payload, ["device_name", "relays", "bms", "temperature_sensors"])
|
||||
assert_keys(payload["relays"][0], ["id", "name", "pin", "enabled"])
|
||||
assert_keys(payload["bms"], ["enabled", "name", "address", "address_type"])
|
||||
assert_keys(payload["temperature_sensors"][0], ["id", "name", "address", "enabled"])
|
||||
|
||||
|
||||
def test_relay_set_contract_fixtures_and_firmware_errors():
|
||||
request = load_fixture("relay_set_request.json")
|
||||
response = load_fixture("relay_set_response.json")
|
||||
|
||||
assert_keys(request, ["id", "state"])
|
||||
assert_keys(response, ["type", "ok", "id", "state"])
|
||||
assert response["type"] == "relay_response"
|
||||
assert response["ok"] is True
|
||||
|
||||
source = firmware_source()
|
||||
assert '"invalid_json"' in source
|
||||
assert '"unknown_relay"' in source
|
||||
|
||||
|
||||
def test_temp_scan_assign_clear_contract_fixtures():
|
||||
scan = load_fixture("temp_scan_response.json")
|
||||
assign_request = load_fixture("temp_assign_request.json")
|
||||
assign_response = load_fixture("temp_assign_response.json")
|
||||
clear_request = load_fixture("temp_clear_request.json")
|
||||
clear_response = load_fixture("temp_clear_response.json")
|
||||
|
||||
assert_keys(scan, ["type", "ok", "devices"])
|
||||
assert scan["type"] == "temp_scan_response"
|
||||
assert_keys(scan["devices"][0], ["index", "address"])
|
||||
|
||||
assert_keys(assign_request, ["id", "index"])
|
||||
assert_keys(assign_response, ["device_name", "relays", "bms", "temperature_sensors"])
|
||||
assert assign_response["temperature_sensors"][0]["address"]
|
||||
assert assign_response["temperature_sensors"][0]["enabled"] is True
|
||||
|
||||
assert_keys(clear_request, ["id"])
|
||||
assert_keys(clear_response, ["device_name", "relays", "bms", "temperature_sensors"])
|
||||
assert clear_response["temperature_sensors"][0]["address"] == ""
|
||||
assert clear_response["temperature_sensors"][0]["enabled"] is False
|
||||
|
||||
source = firmware_source()
|
||||
assert '"unknown_temp_sensor"' in source
|
||||
assert '"invalid_temp_selection"' in source
|
||||
|
||||
|
||||
def test_wifi_config_contract_fixtures():
|
||||
request = load_fixture("wifi_config_request.json")
|
||||
response = load_fixture("wifi_config_response.json")
|
||||
|
||||
assert_keys(request, ["networks"])
|
||||
assert_keys(request["networks"][0], ["ssid", "password", "priority"])
|
||||
assert_keys(response, ["type", "ok", "wifi"])
|
||||
assert response["type"] == "wifi_config_response"
|
||||
assert response["ok"] is True
|
||||
assert_keys(response["wifi"], [
|
||||
"ap_enabled",
|
||||
"sta_enabled",
|
||||
"network_count",
|
||||
"active_ssid",
|
||||
"sta_connected",
|
||||
"ap_ip",
|
||||
"sta_ip",
|
||||
"networks",
|
||||
])
|
||||
assert_keys(response["wifi"]["networks"][0], [
|
||||
"index",
|
||||
"ssid",
|
||||
"priority",
|
||||
"password_set",
|
||||
])
|
||||
|
||||
|
||||
def test_http_error_fixture_and_known_error_codes_are_stable():
|
||||
payload = load_fixture("error_response.json")
|
||||
source = firmware_source()
|
||||
|
||||
assert payload == {"ok": False, "error": "invalid_json"}
|
||||
|
||||
for error_code in [
|
||||
"invalid_json",
|
||||
"unknown_relay",
|
||||
"unknown_temp_sensor",
|
||||
"invalid_temp_selection",
|
||||
"invalid_bms_selection",
|
||||
"invalid_relay_route",
|
||||
"missing_relay_action",
|
||||
"invalid_relay_action",
|
||||
]:
|
||||
assert error_code in source
|
||||
Reference in New Issue
Block a user