Files
wled-screen-controller-mixed/server/tests/test_processor_manager.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

255 lines
6.9 KiB
Python

"""Tests for processor manager."""
import asyncio
import pytest
import respx
from httpx import Response
from wled_controller.core.processor_manager import (
ProcessorManager,
ProcessingSettings,
)
from wled_controller.core.calibration import create_default_calibration
@pytest.fixture
def wled_url():
"""Provide test WLED device URL."""
return "http://192.168.1.100"
@pytest.fixture
def mock_wled_responses():
"""Provide mock WLED API responses."""
return {
"info": {
"name": "Test WLED",
"ver": "0.14.0",
"leds": {"count": 150},
"brand": "WLED",
"product": "FOSS",
"mac": "AA:BB:CC:DD:EE:FF",
"ip": "192.168.1.100",
},
"state": {"on": True, "bri": 255},
}
@pytest.fixture
def processor_manager():
"""Provide processor manager instance."""
return ProcessorManager()
def test_processor_manager_init():
"""Test processor manager initialization."""
manager = ProcessorManager()
assert manager is not None
assert manager.get_all_devices() == []
def test_add_device(processor_manager):
"""Test adding a device."""
processor_manager.add_device(
device_id="test_device",
device_url="http://192.168.1.100",
led_count=150,
)
devices = processor_manager.get_all_devices()
assert "test_device" in devices
def test_add_device_duplicate(processor_manager):
"""Test adding duplicate device fails."""
processor_manager.add_device(
device_id="test_device",
device_url="http://192.168.1.100",
led_count=150,
)
with pytest.raises(ValueError, match="already exists"):
processor_manager.add_device(
device_id="test_device",
device_url="http://192.168.1.100",
led_count=150,
)
def test_remove_device(processor_manager):
"""Test removing a device."""
processor_manager.add_device(
device_id="test_device",
device_url="http://192.168.1.100",
led_count=150,
)
processor_manager.remove_device("test_device")
assert "test_device" not in processor_manager.get_all_devices()
def test_remove_device_not_found(processor_manager):
"""Test removing non-existent device fails."""
with pytest.raises(ValueError, match="not found"):
processor_manager.remove_device("nonexistent")
def test_update_settings(processor_manager):
"""Test updating device settings."""
processor_manager.add_device(
device_id="test_device",
device_url="http://192.168.1.100",
led_count=150,
)
new_settings = ProcessingSettings(
display_index=1,
fps=60,
border_width=20,
)
processor_manager.update_settings("test_device", new_settings)
# Verify settings updated
state = processor_manager.get_state("test_device")
assert state["fps_target"] == 60
def test_update_calibration(processor_manager):
"""Test updating device calibration."""
processor_manager.add_device(
device_id="test_device",
device_url="http://192.168.1.100",
led_count=150,
)
new_calibration = create_default_calibration(150)
processor_manager.update_calibration("test_device", new_calibration)
def test_update_calibration_led_count_mismatch(processor_manager):
"""Test updating calibration with mismatched LED count fails."""
processor_manager.add_device(
device_id="test_device",
device_url="http://192.168.1.100",
led_count=150,
)
wrong_calibration = create_default_calibration(100) # Wrong count
with pytest.raises(ValueError, match="does not match"):
processor_manager.update_calibration("test_device", wrong_calibration)
@pytest.mark.asyncio
@respx.mock
async def test_start_processing(processor_manager, wled_url, mock_wled_responses):
"""Test starting processing."""
respx.get(f"{wled_url}/json/info").mock(
return_value=Response(200, json=mock_wled_responses["info"])
)
respx.post(f"{wled_url}/json/state").mock(
return_value=Response(200, json={"success": True})
)
processor_manager.add_device(
device_id="test_device",
device_url=wled_url,
led_count=150,
settings=ProcessingSettings(fps=5), # Low FPS for testing
)
await processor_manager.start_processing("test_device")
assert processor_manager.is_processing("test_device") is True
# Let it process a few frames
await asyncio.sleep(0.5)
# Stop processing
await processor_manager.stop_processing("test_device")
assert processor_manager.is_processing("test_device") is False
@pytest.mark.asyncio
async def test_start_processing_already_running(processor_manager):
"""Test starting processing when already running fails."""
# This test would need mocked WLED responses
# Skipping actual connection for simplicity
pass
@pytest.mark.asyncio
async def test_stop_processing_not_running(processor_manager):
"""Test stopping processing when not running."""
processor_manager.add_device(
device_id="test_device",
device_url="http://192.168.1.100",
led_count=150,
)
# Should not raise error
await processor_manager.stop_processing("test_device")
def test_get_state(processor_manager):
"""Test getting device state."""
processor_manager.add_device(
device_id="test_device",
device_url="http://192.168.1.100",
led_count=150,
settings=ProcessingSettings(fps=30, display_index=0),
)
state = processor_manager.get_state("test_device")
assert state["device_id"] == "test_device"
assert state["processing"] is False
assert state["fps_target"] == 30
assert state["display_index"] == 0
def test_get_state_not_found(processor_manager):
"""Test getting state for non-existent device."""
with pytest.raises(ValueError, match="not found"):
processor_manager.get_state("nonexistent")
def test_get_metrics(processor_manager):
"""Test getting device metrics."""
processor_manager.add_device(
device_id="test_device",
device_url="http://192.168.1.100",
led_count=150,
)
metrics = processor_manager.get_metrics("test_device")
assert metrics["device_id"] == "test_device"
assert metrics["processing"] is False
assert metrics["frames_processed"] == 0
assert metrics["errors_count"] == 0
@pytest.mark.asyncio
async def test_stop_all(processor_manager):
"""Test stopping all processors."""
processor_manager.add_device(
device_id="test_device1",
device_url="http://192.168.1.100",
led_count=150,
)
processor_manager.add_device(
device_id="test_device2",
device_url="http://192.168.1.101",
led_count=150,
)
await processor_manager.stop_all()
assert processor_manager.is_processing("test_device1") is False
assert processor_manager.is_processing("test_device2") is False