Some checks failed
Lint & Test / test (push) Failing after 1m33s
All store tests were passing file paths instead of Database objects after the JSON-to-SQLite migration. Updated fixtures to create temp Database instances, rewrote backup e2e tests for binary .db format, and fixed config tests for the simplified StorageConfig.
204 lines
6.3 KiB
Python
204 lines
6.3 KiB
Python
"""Tests for device CRUD routes.
|
|
|
|
These tests exercise the FastAPI route handlers using dependency override
|
|
to inject test stores, avoiding real hardware dependencies.
|
|
"""
|
|
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
|
|
import pytest
|
|
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
|
|
from wled_controller.api.routes.devices import router
|
|
from wled_controller.storage.device_store import Device, DeviceStore
|
|
from wled_controller.storage.output_target_store import OutputTargetStore
|
|
from wled_controller.core.processing.processor_manager import ProcessorManager
|
|
from wled_controller.api import dependencies as deps
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# App + fixtures (isolated from the real main app)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _make_app():
|
|
"""Build a minimal FastAPI app with just the devices router + overrides."""
|
|
app = FastAPI()
|
|
app.include_router(router)
|
|
return app
|
|
|
|
|
|
@pytest.fixture
|
|
def _route_db(tmp_path):
|
|
from wled_controller.storage.database import Database
|
|
db = Database(tmp_path / "test.db")
|
|
yield db
|
|
db.close()
|
|
|
|
|
|
@pytest.fixture
|
|
def device_store(_route_db):
|
|
return DeviceStore(_route_db)
|
|
|
|
|
|
@pytest.fixture
|
|
def output_target_store(_route_db):
|
|
return OutputTargetStore(_route_db)
|
|
|
|
|
|
@pytest.fixture
|
|
def processor_manager():
|
|
"""A mock ProcessorManager — avoids real hardware."""
|
|
m = MagicMock(spec=ProcessorManager)
|
|
m.add_device = MagicMock()
|
|
m.remove_device = AsyncMock()
|
|
m.update_device_info = MagicMock()
|
|
m.find_device_state = MagicMock(return_value=None)
|
|
m.get_all_device_health_dicts = MagicMock(return_value=[])
|
|
return m
|
|
|
|
|
|
@pytest.fixture
|
|
def client(device_store, output_target_store, processor_manager):
|
|
app = _make_app()
|
|
|
|
# Override auth to always pass
|
|
from wled_controller.api.auth import verify_api_key
|
|
app.dependency_overrides[verify_api_key] = lambda: "test-user"
|
|
|
|
# Override stores and manager
|
|
app.dependency_overrides[deps.get_device_store] = lambda: device_store
|
|
app.dependency_overrides[deps.get_output_target_store] = lambda: output_target_store
|
|
app.dependency_overrides[deps.get_processor_manager] = lambda: processor_manager
|
|
|
|
return TestClient(app, raise_server_exceptions=False)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helper to pre-populate a device
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _seed_device(store: DeviceStore, name="Test Device", led_count=100) -> Device:
|
|
return store.create_device(
|
|
name=name,
|
|
url="http://192.168.1.100",
|
|
led_count=led_count,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# LIST
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestListDevices:
|
|
def test_list_empty(self, client):
|
|
resp = client.get("/api/v1/devices")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["count"] == 0
|
|
assert data["devices"] == []
|
|
|
|
def test_list_with_devices(self, client, device_store):
|
|
_seed_device(device_store, "Dev A")
|
|
_seed_device(device_store, "Dev B")
|
|
resp = client.get("/api/v1/devices")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["count"] == 2
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# GET by ID
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestGetDevice:
|
|
def test_get_existing(self, client, device_store):
|
|
d = _seed_device(device_store)
|
|
resp = client.get(f"/api/v1/devices/{d.id}")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["id"] == d.id
|
|
assert data["name"] == "Test Device"
|
|
|
|
def test_get_not_found(self, client):
|
|
resp = client.get("/api/v1/devices/nonexistent")
|
|
assert resp.status_code == 404
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# UPDATE
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestUpdateDevice:
|
|
def test_update_name(self, client, device_store):
|
|
d = _seed_device(device_store)
|
|
resp = client.put(
|
|
f"/api/v1/devices/{d.id}",
|
|
json={"name": "Renamed"},
|
|
)
|
|
assert resp.status_code == 200
|
|
assert resp.json()["name"] == "Renamed"
|
|
|
|
def test_update_led_count(self, client, device_store):
|
|
d = _seed_device(device_store, led_count=100)
|
|
resp = client.put(
|
|
f"/api/v1/devices/{d.id}",
|
|
json={"led_count": 300},
|
|
)
|
|
assert resp.status_code == 200
|
|
assert resp.json()["led_count"] == 300
|
|
|
|
def test_update_not_found(self, client):
|
|
resp = client.put(
|
|
"/api/v1/devices/missing_id",
|
|
json={"name": "X"},
|
|
)
|
|
assert resp.status_code == 404
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# DELETE
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestDeleteDevice:
|
|
def test_delete_existing(self, client, device_store):
|
|
d = _seed_device(device_store)
|
|
resp = client.delete(f"/api/v1/devices/{d.id}")
|
|
assert resp.status_code == 204
|
|
assert device_store.count() == 0
|
|
|
|
def test_delete_not_found(self, client):
|
|
resp = client.delete("/api/v1/devices/missing_id")
|
|
assert resp.status_code == 404
|
|
|
|
def test_delete_referenced_by_target_returns_409(
|
|
self, client, device_store, output_target_store
|
|
):
|
|
d = _seed_device(device_store)
|
|
output_target_store.create_target(
|
|
name="Target",
|
|
target_type="led",
|
|
device_id=d.id,
|
|
)
|
|
resp = client.delete(f"/api/v1/devices/{d.id}")
|
|
assert resp.status_code == 409
|
|
assert "referenced" in resp.json()["detail"].lower()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Batch states
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestBatchStates:
|
|
def test_batch_states(self, client):
|
|
resp = client.get("/api/v1/devices/batch/states")
|
|
assert resp.status_code == 200
|
|
assert "states" in resp.json()
|