""" 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