Files
wled-screen-controller-mixed/server/tests/storage/test_value_source_store.py
alexei.dolgolyov f2871319cb
Some checks failed
Lint & Test / test (push) Failing after 48s
refactor: comprehensive code quality, security, and release readiness improvements
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.
2026-03-22 00:38:28 +03:00

260 lines
8.6 KiB
Python

"""Tests for ValueSourceStore — CRUD for all source types."""
import pytest
from wled_controller.storage.value_source import (
AdaptiveValueSource,
AnimatedValueSource,
AudioValueSource,
DaylightValueSource,
StaticValueSource,
ValueSource,
)
from wled_controller.storage.value_source_store import ValueSourceStore
@pytest.fixture
def store(tmp_path) -> ValueSourceStore:
return ValueSourceStore(str(tmp_path / "value_sources.json"))
# ---------------------------------------------------------------------------
# ValueSource model round-trips
# ---------------------------------------------------------------------------
class TestValueSourceModels:
def test_static_round_trip(self):
data = {
"id": "vs_1",
"name": "Static",
"source_type": "static",
"value": 0.75,
"created_at": "2025-01-01T00:00:00+00:00",
"updated_at": "2025-01-01T00:00:00+00:00",
}
src = ValueSource.from_dict(data)
assert isinstance(src, StaticValueSource)
assert src.value == 0.75
restored = ValueSource.from_dict(src.to_dict())
assert restored.value == 0.75
def test_animated_round_trip(self):
data = {
"id": "vs_2",
"name": "Wave",
"source_type": "animated",
"waveform": "triangle",
"speed": 30.0,
"min_value": 0.2,
"max_value": 0.8,
"created_at": "2025-01-01T00:00:00+00:00",
"updated_at": "2025-01-01T00:00:00+00:00",
}
src = ValueSource.from_dict(data)
assert isinstance(src, AnimatedValueSource)
assert src.waveform == "triangle"
assert src.speed == 30.0
def test_audio_round_trip(self):
data = {
"id": "vs_3",
"name": "Audio",
"source_type": "audio",
"audio_source_id": "as_1",
"mode": "peak",
"sensitivity": 2.0,
"smoothing": 0.5,
"auto_gain": True,
"created_at": "2025-01-01T00:00:00+00:00",
"updated_at": "2025-01-01T00:00:00+00:00",
}
src = ValueSource.from_dict(data)
assert isinstance(src, AudioValueSource)
assert src.mode == "peak"
assert src.auto_gain is True
def test_adaptive_time_round_trip(self):
data = {
"id": "vs_4",
"name": "Time",
"source_type": "adaptive_time",
"schedule": [
{"time": "00:00", "value": 0.1},
{"time": "12:00", "value": 1.0},
],
"created_at": "2025-01-01T00:00:00+00:00",
"updated_at": "2025-01-01T00:00:00+00:00",
}
src = ValueSource.from_dict(data)
assert isinstance(src, AdaptiveValueSource)
assert len(src.schedule) == 2
def test_adaptive_scene_round_trip(self):
data = {
"id": "vs_5",
"name": "Scene",
"source_type": "adaptive_scene",
"picture_source_id": "ps_1",
"scene_behavior": "match",
"created_at": "2025-01-01T00:00:00+00:00",
"updated_at": "2025-01-01T00:00:00+00:00",
}
src = ValueSource.from_dict(data)
assert isinstance(src, AdaptiveValueSource)
assert src.scene_behavior == "match"
def test_daylight_round_trip(self):
data = {
"id": "vs_6",
"name": "Daylight",
"source_type": "daylight",
"speed": 2.0,
"use_real_time": True,
"latitude": 55.0,
"created_at": "2025-01-01T00:00:00+00:00",
"updated_at": "2025-01-01T00:00:00+00:00",
}
src = ValueSource.from_dict(data)
assert isinstance(src, DaylightValueSource)
assert src.use_real_time is True
assert src.latitude == 55.0
def test_unknown_type_defaults_to_static(self):
data = {
"id": "vs_u",
"name": "Unknown",
"source_type": "unknown_future",
"value": 0.5,
"created_at": "2025-01-01T00:00:00+00:00",
"updated_at": "2025-01-01T00:00:00+00:00",
}
src = ValueSource.from_dict(data)
assert isinstance(src, StaticValueSource)
# ---------------------------------------------------------------------------
# ValueSourceStore CRUD
# ---------------------------------------------------------------------------
class TestValueSourceStoreCRUD:
def test_create_static(self, store):
s = store.create_source(name="S1", source_type="static", value=0.5)
assert s.id.startswith("vs_")
assert isinstance(s, StaticValueSource)
assert s.value == 0.5
assert store.count() == 1
def test_create_animated(self, store):
s = store.create_source(
name="A1", source_type="animated",
waveform="sawtooth", speed=20.0,
)
assert isinstance(s, AnimatedValueSource)
assert s.waveform == "sawtooth"
assert s.speed == 20.0
def test_create_audio(self, store):
s = store.create_source(
name="Au1", source_type="audio",
audio_source_id="as_1", mode="beat",
)
assert isinstance(s, AudioValueSource)
assert s.mode == "beat"
def test_create_adaptive_time(self, store):
schedule = [
{"time": "08:00", "value": 0.5},
{"time": "20:00", "value": 1.0},
]
s = store.create_source(
name="AT", source_type="adaptive_time", schedule=schedule,
)
assert isinstance(s, AdaptiveValueSource)
assert len(s.schedule) == 2
def test_create_adaptive_time_insufficient_schedule(self, store):
with pytest.raises(ValueError, match="at least 2 points"):
store.create_source(
name="Bad", source_type="adaptive_time",
schedule=[{"time": "12:00", "value": 0.5}],
)
def test_create_daylight(self, store):
s = store.create_source(
name="DL", source_type="daylight",
speed=2.0, use_real_time=True, latitude=48.0,
)
assert isinstance(s, DaylightValueSource)
assert s.use_real_time is True
def test_create_invalid_type(self, store):
with pytest.raises(ValueError, match="Invalid source type"):
store.create_source(name="Bad", source_type="invalid")
def test_get_all(self, store):
store.create_source("A", "static")
store.create_source("B", "static")
assert len(store.get_all_sources()) == 2
def test_get(self, store):
created = store.create_source("Get", "static", value=0.3)
got = store.get_source(created.id)
assert got.name == "Get"
def test_delete(self, store):
s = store.create_source("Del", "static")
store.delete_source(s.id)
assert store.count() == 0
def test_update_static(self, store):
s = store.create_source("Stat", "static", value=0.5)
updated = store.update_source(s.id, value=0.9)
assert isinstance(updated, StaticValueSource)
assert updated.value == 0.9
def test_update_name(self, store):
s = store.create_source("Old", "static")
updated = store.update_source(s.id, name="New")
assert updated.name == "New"
def test_update_animated_fields(self, store):
s = store.create_source("Anim", "animated", waveform="sine", speed=10.0)
updated = store.update_source(s.id, waveform="square", speed=30.0)
assert isinstance(updated, AnimatedValueSource)
assert updated.waveform == "square"
assert updated.speed == 30.0
# ---------------------------------------------------------------------------
# Name uniqueness
# ---------------------------------------------------------------------------
class TestValueSourceNameUniqueness:
def test_duplicate_name(self, store):
store.create_source("Same", "static")
with pytest.raises(ValueError, match="already exists"):
store.create_source("Same", "animated")
# ---------------------------------------------------------------------------
# Persistence
# ---------------------------------------------------------------------------
class TestValueSourcePersistence:
def test_persist_and_reload(self, tmp_path):
path = str(tmp_path / "vs_persist.json")
s1 = ValueSourceStore(path)
src = s1.create_source("Persist", "static", value=0.42)
sid = src.id
s2 = ValueSourceStore(path)
loaded = s2.get_source(sid)
assert loaded.name == "Persist"
assert isinstance(loaded, StaticValueSource)
assert loaded.value == 0.42