134 lines
4.7 KiB
Python
134 lines
4.7 KiB
Python
"""
|
|
Tests for the REST API endpoints.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
from app.device_store import DeviceStore
|
|
|
|
|
|
class TestDevicesApi:
|
|
"""Tests for /api/devices endpoints."""
|
|
|
|
def test_list_devices(self, client: TestClient):
|
|
"""GET /api/devices returns all devices."""
|
|
resp = client.get("/api/devices")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["count"] == 3
|
|
assert len(data["devices"]) == 3
|
|
|
|
def test_get_device(self, client: TestClient):
|
|
"""GET /api/devices/{id} returns device detail."""
|
|
resp = client.get("/api/devices/test-plant-01")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["id"] == "test-plant-01"
|
|
assert data["name"] == "Test Plant"
|
|
|
|
def test_get_device_not_found(self, client: TestClient):
|
|
"""GET /api/devices/{id} returns 404 for missing device."""
|
|
resp = client.get("/api/devices/nonexistent")
|
|
assert resp.status_code == 404
|
|
|
|
def test_get_readings_empty(self, client: TestClient):
|
|
"""GET /api/devices/{id}/readings returns empty when no data."""
|
|
resp = client.get("/api/devices/test-plant-01/readings")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["device_id"] == "test-plant-01"
|
|
assert data["readings"] == {}
|
|
|
|
def test_get_readings_with_data(self, client: TestClient, store: DeviceStore):
|
|
"""GET /api/devices/{id}/readings returns stored readings."""
|
|
# Populate store
|
|
loop = asyncio.get_event_loop()
|
|
loop.run_until_complete(
|
|
store.update_reading("test-plant-01", "sensors/temperature", 25.0, "celsius")
|
|
)
|
|
|
|
resp = client.get("/api/devices/test-plant-01/readings")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert "sensors/temperature" in data["readings"]
|
|
assert data["readings"]["sensors/temperature"]["value"] == 25.0
|
|
|
|
def test_server_status(self, client: TestClient):
|
|
"""GET /api/status returns server info."""
|
|
resp = client.get("/api/status")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["server"] == "demeter"
|
|
assert data["devices_total"] == 3
|
|
assert "active_subscriptions" in data
|
|
|
|
|
|
class TestCoapBridge:
|
|
"""Tests for /api/devices/{id}/coap/ bridge endpoints."""
|
|
|
|
def test_coap_get(self, client: TestClient):
|
|
"""GET bridge proxies CoAP request and returns response."""
|
|
resp = client.get("/api/devices/test-plant-01/coap/sensors/temperature")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["device_id"] == "test-plant-01"
|
|
assert data["method"] == "GET"
|
|
assert "response" in data
|
|
|
|
def test_coap_get_not_found(self, client: TestClient):
|
|
"""GET bridge returns 404 for missing device."""
|
|
resp = client.get("/api/devices/missing/coap/sensors/temperature")
|
|
assert resp.status_code == 404
|
|
|
|
def test_coap_put(self, client: TestClient):
|
|
"""PUT bridge proxies CoAP request."""
|
|
resp = client.put(
|
|
"/api/devices/test-plant-01/coap/config/interval",
|
|
json={"interval": 10},
|
|
)
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["method"] == "PUT"
|
|
|
|
|
|
class TestMetricsEndpoint:
|
|
"""Tests for the /metrics Prometheus endpoint."""
|
|
|
|
def test_metrics_returns_text(self, client: TestClient):
|
|
"""GET /metrics returns Prometheus text format."""
|
|
resp = client.get("/metrics")
|
|
assert resp.status_code == 200
|
|
assert "text/plain" in resp.headers["content-type"]
|
|
# Should contain our metric names
|
|
assert "demeter_server_info" in resp.text
|
|
|
|
|
|
class TestDashboard:
|
|
"""Tests for dashboard HTML routes."""
|
|
|
|
def test_root_redirects(self, client: TestClient):
|
|
"""GET / redirects to /dashboard."""
|
|
resp = client.get("/", follow_redirects=False)
|
|
assert resp.status_code == 307
|
|
assert "/dashboard" in resp.headers["location"]
|
|
|
|
def test_dashboard_renders(self, client: TestClient):
|
|
"""GET /dashboard returns HTML."""
|
|
resp = client.get("/dashboard")
|
|
assert resp.status_code == 200
|
|
assert "text/html" in resp.headers["content-type"]
|
|
assert "Demeter" in resp.text
|
|
|
|
def test_dashboard_api_readings(self, client: TestClient):
|
|
"""GET /dashboard/api/readings returns JSON."""
|
|
resp = client.get("/dashboard/api/readings")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert "devices" in data
|
|
assert "timestamp" in data
|