871a3cc174
- Add config.py with multi-source config (YAML, .env, env vars) - Add .env.example and config.yaml.example - Add settings API endpoints (GET/POST /api/v1/settings) - Add status endpoint (GET /api/v1/status) with hardware subsystem check - Add settings screen to web client (protected by API token) - Add localization strings for settings UI - Add tests for config module and new endpoints - Update README with config documentation
150 lines
4.8 KiB
Python
150 lines
4.8 KiB
Python
"""Tests for the FastAPI application endpoints."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
from ..main import app
|
|
|
|
|
|
@pytest.fixture
|
|
def client() -> TestClient:
|
|
return TestClient(app)
|
|
|
|
|
|
class TestHealth:
|
|
def test_health_returns_ok(self, client: TestClient) -> None:
|
|
response = client.get("/health")
|
|
assert response.status_code == 200
|
|
assert response.json() == {"status": "ok"}
|
|
|
|
|
|
class TestGetState:
|
|
def test_get_state_returns_dict(self, client: TestClient) -> None:
|
|
with (
|
|
patch.object(app.state, "media", create=True),
|
|
patch.object(app.state, "audio", create=True),
|
|
patch.object(app.state, "notifications", create=True),
|
|
patch.object(app.state, "telemetry", create=True),
|
|
):
|
|
app.state.media.get_state = lambda: {"title": "Test"}
|
|
app.state.audio.get_state = lambda: {"backend": "windows"}
|
|
app.state.notifications.get_active = list
|
|
app.state.telemetry.get_state = dict
|
|
|
|
response = client.get("/api/v1/state")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "media" in data
|
|
assert "audio" in data
|
|
assert "notifications" in data
|
|
assert "telemetry" in data
|
|
|
|
|
|
class TestNotifyEndpoint:
|
|
def test_notify_without_token_returns_401(self, client: TestClient) -> None:
|
|
response = client.post(
|
|
"/api/v1/notify",
|
|
json={
|
|
"type": "alert",
|
|
"title": "Test",
|
|
"message": "Hello",
|
|
},
|
|
)
|
|
assert response.status_code == 401
|
|
|
|
def test_notify_with_valid_token(self, client: TestClient) -> None:
|
|
os.environ["API_TOKEN"] = "test-token-123"
|
|
|
|
with patch.object(app.state, "notifications", create=True) as mock_notif:
|
|
mock_notif.create = AsyncMock()
|
|
mock_notif.create.return_value = type(
|
|
"Notification", (), {"id": "test-id"}
|
|
)()
|
|
|
|
response = client.post(
|
|
"/api/v1/notify",
|
|
json={
|
|
"type": "alert",
|
|
"title": "Build Complete",
|
|
"message": "Success",
|
|
},
|
|
headers={"Authorization": "Bearer test-token-123"},
|
|
)
|
|
assert response.status_code == 200
|
|
assert response.json()["status"] == "created"
|
|
|
|
# Cleanup
|
|
del os.environ["API_TOKEN"]
|
|
|
|
def test_notify_with_wrong_token_returns_401(self, client: TestClient) -> None:
|
|
os.environ["API_TOKEN"] = "correct-token"
|
|
|
|
response = client.post(
|
|
"/api/v1/notify",
|
|
json={"type": "alert", "title": "Test", "message": "Hello"},
|
|
headers={"Authorization": "Bearer wrong-token"},
|
|
)
|
|
assert response.status_code == 401
|
|
|
|
del os.environ["API_TOKEN"]
|
|
|
|
|
|
class TestSettingsEndpoint:
|
|
def test_get_settings_without_token_returns_401(self, client: TestClient) -> None:
|
|
response = client.get("/api/v1/settings")
|
|
assert response.status_code == 401
|
|
|
|
def test_get_settings_with_valid_token(self, client: TestClient) -> None:
|
|
os.environ["API_TOKEN"] = "settings-token"
|
|
|
|
response = client.get(
|
|
"/api/v1/settings",
|
|
headers={"Authorization": "Bearer settings-token"},
|
|
)
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "dashboard_pin" in data
|
|
assert "api_token" in data
|
|
assert "host" in data
|
|
assert "port" in data
|
|
|
|
del os.environ["API_TOKEN"]
|
|
|
|
def test_update_settings(self, client: TestClient) -> None:
|
|
os.environ["API_TOKEN"] = "update-token"
|
|
|
|
response = client.post(
|
|
"/api/v1/settings",
|
|
json={
|
|
"dashboard_pin": "9999",
|
|
"api_token": "new-token",
|
|
},
|
|
headers={"Authorization": "Bearer update-token"},
|
|
)
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["dashboard_pin"] == "9999"
|
|
assert data["api_token"] == "new-token"
|
|
|
|
del os.environ["API_TOKEN"]
|
|
|
|
|
|
class TestStatusEndpoint:
|
|
def test_status_returns_subsystems(self, client: TestClient) -> None:
|
|
# Ensure subsystems are initialized
|
|
if not hasattr(app.state, "subsystems"):
|
|
app.state.subsystems = {}
|
|
|
|
response = client.get("/api/v1/status")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "subsystems" in data
|
|
assert "config" in data
|
|
assert "pin_configured" in data["config"]
|
|
assert "token_configured" in data["config"]
|