Files
wled-screen-controller-mixed/server/tests/test_device_store.py
alexei.dolgolyov d471a40234
Some checks failed
Validate / validate (push) Failing after 1m6s
Initial commit: WLED Screen Controller with FastAPI server and Home Assistant integration
This is a complete WLED ambient lighting controller that captures screen border pixels
and sends them to WLED devices for immersive ambient lighting effects.

## Server Features:
- FastAPI-based REST API with 17+ endpoints
- Real-time screen capture with multi-monitor support
- Advanced LED calibration system with visual GUI
- API key authentication with labeled tokens
- Per-device brightness control (0-100%)
- Configurable FPS (1-60), border width, and color correction
- Persistent device storage (JSON-based)
- Comprehensive Web UI with dark/light themes
- Docker support with docker-compose
- Windows monitor name detection via WMI (shows "LG ULTRAWIDE" etc.)

## Web UI Features:
- Device management (add, configure, remove WLED devices)
- Real-time status monitoring with FPS metrics
- Settings modal for device configuration
- Visual calibration GUI with edge testing
- Brightness slider per device
- Display selection with friendly monitor names
- Token-based authentication with login/logout
- Responsive button layout

## Calibration System:
- Support for any LED strip layout (clockwise/counterclockwise)
- 4 starting position options (corners)
- Per-edge LED count configuration
- Visual preview with starting position indicator
- Test buttons to light up individual edges
- Smart LED ordering based on start position and direction

## Home Assistant Integration:
- Custom HACS integration
- Switch entities for processing control
- Sensor entities for status and FPS
- Select entities for display selection
- Config flow for easy setup
- Auto-discovery of devices from server

## Technical Stack:
- Python 3.11+
- FastAPI + uvicorn
- mss (screen capture)
- httpx (async WLED client)
- Pydantic (validation)
- WMI (Windows monitor detection)
- Structlog (logging)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-06 16:38:27 +03:00

306 lines
8.0 KiB
Python

"""Tests for device storage."""
import pytest
from pathlib import Path
from wled_controller.storage.device_store import Device, DeviceStore
from wled_controller.core.processor_manager import ProcessingSettings
from wled_controller.core.calibration import create_default_calibration
@pytest.fixture
def temp_storage(tmp_path):
"""Provide temporary storage file."""
return tmp_path / "devices.json"
@pytest.fixture
def device_store(temp_storage):
"""Provide device store instance."""
return DeviceStore(temp_storage)
def test_device_creation():
"""Test creating a device."""
device = Device(
device_id="test_001",
name="Test Device",
url="http://192.168.1.100",
led_count=150,
)
assert device.id == "test_001"
assert device.name == "Test Device"
assert device.url == "http://192.168.1.100"
assert device.led_count == 150
assert device.enabled is True
def test_device_to_dict():
"""Test converting device to dictionary."""
device = Device(
device_id="test_001",
name="Test Device",
url="http://192.168.1.100",
led_count=150,
)
data = device.to_dict()
assert data["id"] == "test_001"
assert data["name"] == "Test Device"
assert data["url"] == "http://192.168.1.100"
assert data["led_count"] == 150
assert "settings" in data
assert "calibration" in data
def test_device_from_dict():
"""Test creating device from dictionary."""
data = {
"id": "test_001",
"name": "Test Device",
"url": "http://192.168.1.100",
"led_count": 150,
"enabled": True,
"settings": {
"display_index": 0,
"fps": 30,
"border_width": 10,
},
}
device = Device.from_dict(data)
assert device.id == "test_001"
assert device.name == "Test Device"
assert device.led_count == 150
def test_device_round_trip():
"""Test converting device to dict and back."""
original = Device(
device_id="test_001",
name="Test Device",
url="http://192.168.1.100",
led_count=150,
)
data = original.to_dict()
restored = Device.from_dict(data)
assert restored.id == original.id
assert restored.name == original.name
assert restored.url == original.url
assert restored.led_count == original.led_count
def test_device_store_init(device_store):
"""Test device store initialization."""
assert device_store is not None
assert device_store.count() == 0
def test_create_device(device_store):
"""Test creating a device in store."""
device = device_store.create_device(
name="Test WLED",
url="http://192.168.1.100",
led_count=150,
)
assert device.id is not None
assert device.name == "Test WLED"
assert device_store.count() == 1
def test_get_device(device_store):
"""Test retrieving a device."""
created = device_store.create_device(
name="Test WLED",
url="http://192.168.1.100",
led_count=150,
)
retrieved = device_store.get_device(created.id)
assert retrieved is not None
assert retrieved.id == created.id
assert retrieved.name == "Test WLED"
def test_get_device_not_found(device_store):
"""Test retrieving non-existent device."""
device = device_store.get_device("nonexistent")
assert device is None
def test_get_all_devices(device_store):
"""Test getting all devices."""
device_store.create_device("Device 1", "http://192.168.1.100", 150)
device_store.create_device("Device 2", "http://192.168.1.101", 200)
devices = device_store.get_all_devices()
assert len(devices) == 2
assert any(d.name == "Device 1" for d in devices)
assert any(d.name == "Device 2" for d in devices)
def test_update_device(device_store):
"""Test updating a device."""
device = device_store.create_device(
name="Test WLED",
url="http://192.168.1.100",
led_count=150,
)
updated = device_store.update_device(
device.id,
name="Updated WLED",
enabled=False,
)
assert updated.name == "Updated WLED"
assert updated.enabled is False
def test_update_device_settings(device_store):
"""Test updating device settings."""
device = device_store.create_device(
name="Test WLED",
url="http://192.168.1.100",
led_count=150,
)
new_settings = ProcessingSettings(fps=60, border_width=20)
updated = device_store.update_device(
device.id,
settings=new_settings,
)
assert updated.settings.fps == 60
assert updated.settings.border_width == 20
def test_update_device_calibration(device_store):
"""Test updating device calibration."""
device = device_store.create_device(
name="Test WLED",
url="http://192.168.1.100",
led_count=150,
)
new_calibration = create_default_calibration(150)
updated = device_store.update_device(
device.id,
calibration=new_calibration,
)
assert updated.calibration is not None
def test_update_device_not_found(device_store):
"""Test updating non-existent device."""
with pytest.raises(ValueError, match="not found"):
device_store.update_device("nonexistent", name="New Name")
def test_delete_device(device_store):
"""Test deleting a device."""
device = device_store.create_device(
name="Test WLED",
url="http://192.168.1.100",
led_count=150,
)
device_store.delete_device(device.id)
assert device_store.count() == 0
assert device_store.get_device(device.id) is None
def test_delete_device_not_found(device_store):
"""Test deleting non-existent device."""
with pytest.raises(ValueError, match="not found"):
device_store.delete_device("nonexistent")
def test_device_exists(device_store):
"""Test checking if device exists."""
device = device_store.create_device(
name="Test WLED",
url="http://192.168.1.100",
led_count=150,
)
assert device_store.device_exists(device.id) is True
assert device_store.device_exists("nonexistent") is False
def test_persistence(temp_storage):
"""Test device persistence across store instances."""
# Create store and add device
store1 = DeviceStore(temp_storage)
device = store1.create_device(
name="Test WLED",
url="http://192.168.1.100",
led_count=150,
)
device_id = device.id
# Create new store instance (loads from file)
store2 = DeviceStore(temp_storage)
# Verify device persisted
loaded_device = store2.get_device(device_id)
assert loaded_device is not None
assert loaded_device.name == "Test WLED"
assert loaded_device.led_count == 150
def test_clear(device_store):
"""Test clearing all devices."""
device_store.create_device("Device 1", "http://192.168.1.100", 150)
device_store.create_device("Device 2", "http://192.168.1.101", 200)
assert device_store.count() == 2
device_store.clear()
assert device_store.count() == 0
def test_update_led_count_resets_calibration(device_store):
"""Test that updating LED count resets calibration."""
device = device_store.create_device(
name="Test WLED",
url="http://192.168.1.100",
led_count=150,
)
original_calibration = device.calibration
# Update LED count
updated = device_store.update_device(device.id, led_count=200)
# Calibration should be reset for new LED count
assert updated.calibration.get_total_leds() == 200
assert updated.calibration != original_calibration
def test_update_calibration_led_count_mismatch(device_store):
"""Test updating calibration with mismatched LED count fails."""
device = device_store.create_device(
name="Test WLED",
url="http://192.168.1.100",
led_count=150,
)
wrong_calibration = create_default_calibration(100)
with pytest.raises(ValueError, match="does not match"):
device_store.update_device(device.id, calibration=wrong_calibration)