refactor: comprehensive code quality, security, and release readiness improvements
Some checks failed
Lint & Test / test (push) Failing after 48s

Security: tighten CORS defaults, add webhook rate limiting, fix XSS in
automations, guard WebSocket JSON.parse, validate ADB address input,
seal debug exception leak, URL-encode WS tokens, CSS.escape in selectors.

Code quality: add Pydantic models for brightness/power endpoints, fix
thread safety and name uniqueness in DeviceStore, immutable update
pattern, split 6 oversized files into 16 focused modules, enable
TypeScript strictNullChecks (741→102 errors), type state variables,
add dom-utils helper, migrate 3 modules from inline onclick to event
delegation, ProcessorDependencies dataclass.

Performance: async store saves, health endpoint log level, command
palette debounce, optimized entity-events comparison, fix service
worker precache list.

Testing: expand from 45 to 293 passing tests — add store tests (141),
route tests (25), core logic tests (42), E2E flow tests (33), organize
into tests/api/, tests/storage/, tests/core/, tests/e2e/.

DevOps: CI test pipeline, pre-commit config, Dockerfile multi-stage
build with non-root user and health check, docker-compose improvements,
version bump to 0.2.0.

Docs: rewrite CLAUDE.md (202→56 lines), server/CLAUDE.md (212→76),
create contexts/server-operations.md, fix .js→.ts references, fix env
var prefix in README, rewrite INSTALLATION.md, add CONTRIBUTING.md and
.env.example.
This commit is contained in:
2026-03-22 00:38:28 +03:00
parent 07bb89e9b7
commit f2871319cb
115 changed files with 9808 additions and 5818 deletions

View File

@@ -1,13 +1,39 @@
"""Pytest configuration and fixtures."""
"""Pytest configuration and shared fixtures."""
import json
import os
from datetime import datetime, timezone
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock
import pytest
from pathlib import Path
from wled_controller.config import Config, StorageConfig, ServerConfig, AuthConfig
from wled_controller.storage.device_store import Device, DeviceStore
from wled_controller.storage.sync_clock import SyncClock
from wled_controller.storage.sync_clock_store import SyncClockStore
from wled_controller.storage.output_target_store import OutputTargetStore
from wled_controller.storage.automation import (
Automation,
AlwaysCondition,
WebhookCondition,
)
from wled_controller.storage.automation_store import AutomationStore
from wled_controller.storage.value_source import StaticValueSource, ValueSource
from wled_controller.storage.value_source_store import ValueSourceStore
# ---------------------------------------------------------------------------
# Directory / path fixtures
# ---------------------------------------------------------------------------
@pytest.fixture
def test_data_dir(tmp_path):
"""Provide a temporary directory for test data."""
return tmp_path / "data"
d = tmp_path / "data"
d.mkdir(parents=True, exist_ok=True)
return d
@pytest.fixture
@@ -16,6 +42,198 @@ def test_config_dir(tmp_path):
return tmp_path / "config"
@pytest.fixture
def temp_store_dir(tmp_path):
"""Provide a temp directory for JSON store files, cleaned up after tests."""
d = tmp_path / "stores"
d.mkdir(parents=True, exist_ok=True)
return d
# ---------------------------------------------------------------------------
# Config fixtures
# ---------------------------------------------------------------------------
@pytest.fixture
def test_config(tmp_path):
"""A Config instance with temp directories for all store files."""
data_dir = tmp_path / "data"
data_dir.mkdir(parents=True, exist_ok=True)
storage = StorageConfig(
devices_file=str(data_dir / "devices.json"),
templates_file=str(data_dir / "capture_templates.json"),
postprocessing_templates_file=str(data_dir / "postprocessing_templates.json"),
picture_sources_file=str(data_dir / "picture_sources.json"),
output_targets_file=str(data_dir / "output_targets.json"),
pattern_templates_file=str(data_dir / "pattern_templates.json"),
color_strip_sources_file=str(data_dir / "color_strip_sources.json"),
audio_sources_file=str(data_dir / "audio_sources.json"),
audio_templates_file=str(data_dir / "audio_templates.json"),
value_sources_file=str(data_dir / "value_sources.json"),
automations_file=str(data_dir / "automations.json"),
scene_presets_file=str(data_dir / "scene_presets.json"),
color_strip_processing_templates_file=str(data_dir / "color_strip_processing_templates.json"),
sync_clocks_file=str(data_dir / "sync_clocks.json"),
)
return Config(
server=ServerConfig(host="127.0.0.1", port=9999),
auth=AuthConfig(api_keys={"test": "test-api-key-12345"}),
storage=storage,
)
# ---------------------------------------------------------------------------
# Store fixtures
# ---------------------------------------------------------------------------
@pytest.fixture
def device_store(temp_store_dir):
"""Provide a DeviceStore backed by a temp file."""
return DeviceStore(temp_store_dir / "devices.json")
@pytest.fixture
def sync_clock_store(temp_store_dir):
"""Provide a SyncClockStore backed by a temp file."""
return SyncClockStore(str(temp_store_dir / "sync_clocks.json"))
@pytest.fixture
def output_target_store(temp_store_dir):
"""Provide an OutputTargetStore backed by a temp file."""
return OutputTargetStore(str(temp_store_dir / "output_targets.json"))
@pytest.fixture
def automation_store(temp_store_dir):
"""Provide an AutomationStore backed by a temp file."""
return AutomationStore(str(temp_store_dir / "automations.json"))
@pytest.fixture
def value_source_store(temp_store_dir):
"""Provide a ValueSourceStore backed by a temp file."""
return ValueSourceStore(str(temp_store_dir / "value_sources.json"))
# ---------------------------------------------------------------------------
# Sample entity factories
# ---------------------------------------------------------------------------
@pytest.fixture
def sample_device():
"""Provide a sample device configuration dict."""
return {
"id": "test_device_001",
"name": "Test WLED Device",
"url": "http://192.168.1.100",
"led_count": 150,
"enabled": True,
"device_type": "wled",
}
@pytest.fixture
def make_device():
"""Factory fixture: call make_device(name=..., **overrides) to build a Device."""
_counter = 0
def _factory(name=None, **kwargs):
nonlocal _counter
_counter += 1
defaults = dict(
device_id=f"device_test_{_counter:04d}",
name=name or f"Device {_counter}",
url=f"http://192.168.1.{_counter}",
led_count=150,
)
defaults.update(kwargs)
return Device(**defaults)
return _factory
@pytest.fixture
def make_sync_clock():
"""Factory fixture: call make_sync_clock(name=..., **overrides)."""
_counter = 0
def _factory(name=None, **kwargs):
nonlocal _counter
_counter += 1
now = datetime.now(timezone.utc)
defaults = dict(
id=f"sc_test_{_counter:04d}",
name=name or f"Clock {_counter}",
speed=1.0,
created_at=now,
updated_at=now,
)
defaults.update(kwargs)
return SyncClock(**defaults)
return _factory
@pytest.fixture
def make_automation():
"""Factory fixture: call make_automation(name=..., **overrides)."""
_counter = 0
def _factory(name=None, **kwargs):
nonlocal _counter
_counter += 1
now = datetime.now(timezone.utc)
defaults = dict(
id=f"auto_test_{_counter:04d}",
name=name or f"Automation {_counter}",
enabled=True,
condition_logic="or",
conditions=[],
scene_preset_id=None,
deactivation_mode="none",
deactivation_scene_preset_id=None,
created_at=now,
updated_at=now,
)
defaults.update(kwargs)
return Automation(**defaults)
return _factory
# ---------------------------------------------------------------------------
# Authenticated test client
# ---------------------------------------------------------------------------
@pytest.fixture
def authenticated_client(test_config, monkeypatch):
"""Provide a FastAPI TestClient with auth header pre-set.
Patches global config so the app uses temp storage paths.
"""
import wled_controller.config as config_mod
monkeypatch.setattr(config_mod, "config", test_config)
from fastapi.testclient import TestClient
from wled_controller.main import app
client = TestClient(app, raise_server_exceptions=False)
client.headers["Authorization"] = "Bearer test-api-key-12345"
return client
# ---------------------------------------------------------------------------
# Calibration sample (kept from original conftest)
# ---------------------------------------------------------------------------
@pytest.fixture
def sample_calibration():
"""Provide a sample calibration configuration."""
@@ -29,21 +247,3 @@ def sample_calibration():
{"edge": "left", "led_start": 110, "led_count": 40, "reverse": True},
],
}
@pytest.fixture
def sample_device():
"""Provide a sample device configuration."""
return {
"id": "test_device_001",
"name": "Test WLED Device",
"url": "http://192.168.1.100",
"led_count": 150,
"enabled": True,
"settings": {
"display_index": 0,
"fps": 30,
"border_width": 10,
"brightness": 0.8,
},
}