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.
212 lines
7.2 KiB
Python
212 lines
7.2 KiB
Python
"""Tests for OutputTargetStore — CRUD for LED and key_colors targets."""
|
|
|
|
import pytest
|
|
|
|
from wled_controller.storage.output_target import OutputTarget
|
|
from wled_controller.storage.output_target_store import OutputTargetStore
|
|
from wled_controller.storage.wled_output_target import WledOutputTarget
|
|
from wled_controller.storage.key_colors_output_target import (
|
|
KeyColorsOutputTarget,
|
|
KeyColorsSettings,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def store(tmp_path) -> OutputTargetStore:
|
|
return OutputTargetStore(str(tmp_path / "output_targets.json"))
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# OutputTarget model dispatching
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestOutputTargetModel:
|
|
def test_led_from_dict(self):
|
|
data = {
|
|
"id": "pt_1",
|
|
"name": "LED Target",
|
|
"target_type": "led",
|
|
"device_id": "dev_1",
|
|
"color_strip_source_id": "css_1",
|
|
"fps": 30,
|
|
"protocol": "ddp",
|
|
"created_at": "2025-01-01T00:00:00+00:00",
|
|
"updated_at": "2025-01-01T00:00:00+00:00",
|
|
}
|
|
target = OutputTarget.from_dict(data)
|
|
assert isinstance(target, WledOutputTarget)
|
|
assert target.device_id == "dev_1"
|
|
|
|
def test_key_colors_from_dict(self):
|
|
data = {
|
|
"id": "pt_2",
|
|
"name": "KC Target",
|
|
"target_type": "key_colors",
|
|
"picture_source_id": "ps_1",
|
|
"settings": {},
|
|
"created_at": "2025-01-01T00:00:00+00:00",
|
|
"updated_at": "2025-01-01T00:00:00+00:00",
|
|
}
|
|
target = OutputTarget.from_dict(data)
|
|
assert isinstance(target, KeyColorsOutputTarget)
|
|
|
|
def test_unknown_type_raises(self):
|
|
data = {
|
|
"id": "pt_3",
|
|
"name": "Bad",
|
|
"target_type": "nonexistent",
|
|
"created_at": "2025-01-01T00:00:00+00:00",
|
|
"updated_at": "2025-01-01T00:00:00+00:00",
|
|
}
|
|
with pytest.raises(ValueError, match="Unknown target type"):
|
|
OutputTarget.from_dict(data)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# OutputTargetStore CRUD
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestOutputTargetStoreCRUD:
|
|
def test_create_led_target(self, store):
|
|
t = store.create_target(
|
|
name="LED 1",
|
|
target_type="led",
|
|
device_id="dev_1",
|
|
color_strip_source_id="css_1",
|
|
fps=60,
|
|
protocol="ddp",
|
|
)
|
|
assert t.id.startswith("pt_")
|
|
assert isinstance(t, WledOutputTarget)
|
|
assert t.name == "LED 1"
|
|
assert store.count() == 1
|
|
|
|
def test_create_key_colors_target(self, store):
|
|
t = store.create_target(
|
|
name="KC 1",
|
|
target_type="key_colors",
|
|
picture_source_id="ps_1",
|
|
)
|
|
assert isinstance(t, KeyColorsOutputTarget)
|
|
assert t.picture_source_id == "ps_1"
|
|
|
|
def test_create_invalid_type(self, store):
|
|
with pytest.raises(ValueError, match="Invalid target type"):
|
|
store.create_target(name="Bad", target_type="invalid")
|
|
|
|
def test_get_all(self, store):
|
|
store.create_target("A", "led")
|
|
store.create_target("B", "led")
|
|
assert len(store.get_all_targets()) == 2
|
|
|
|
def test_get(self, store):
|
|
created = store.create_target("Get", "led")
|
|
got = store.get_target(created.id)
|
|
assert got.name == "Get"
|
|
|
|
def test_delete(self, store):
|
|
t = store.create_target("Del", "led")
|
|
store.delete_target(t.id)
|
|
assert store.count() == 0
|
|
|
|
def test_delete_not_found(self, store):
|
|
with pytest.raises(ValueError, match="not found"):
|
|
store.delete_target("nope")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Update
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestOutputTargetUpdate:
|
|
def test_update_name(self, store):
|
|
t = store.create_target("Old", "led")
|
|
updated = store.update_target(t.id, name="New")
|
|
assert updated.name == "New"
|
|
|
|
def test_update_led_fields(self, store):
|
|
t = store.create_target("LED", "led", fps=30, protocol="ddp")
|
|
updated = store.update_target(t.id, fps=60, protocol="drgb")
|
|
assert isinstance(updated, WledOutputTarget)
|
|
assert updated.fps == 60
|
|
assert updated.protocol == "drgb"
|
|
|
|
def test_update_not_found(self, store):
|
|
with pytest.raises(ValueError, match="not found"):
|
|
store.update_target("nope", name="X")
|
|
|
|
def test_update_tags(self, store):
|
|
t = store.create_target("Tags", "led", tags=["old"])
|
|
updated = store.update_target(t.id, tags=["new", "tags"])
|
|
assert updated.tags == ["new", "tags"]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Name uniqueness
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestOutputTargetNameUniqueness:
|
|
def test_duplicate_name_create(self, store):
|
|
store.create_target("Same", "led")
|
|
with pytest.raises(ValueError, match="already exists"):
|
|
store.create_target("Same", "led")
|
|
|
|
def test_duplicate_name_update(self, store):
|
|
store.create_target("First", "led")
|
|
t2 = store.create_target("Second", "led")
|
|
with pytest.raises(ValueError, match="already exists"):
|
|
store.update_target(t2.id, name="First")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Query helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestOutputTargetQueries:
|
|
def test_get_targets_for_device(self, store):
|
|
store.create_target("T1", "led", device_id="dev_a")
|
|
store.create_target("T2", "led", device_id="dev_b")
|
|
store.create_target("T3", "led", device_id="dev_a")
|
|
|
|
results = store.get_targets_for_device("dev_a")
|
|
assert len(results) == 2
|
|
assert all(isinstance(t, WledOutputTarget) for t in results)
|
|
|
|
def test_get_targets_for_device_empty(self, store):
|
|
assert store.get_targets_for_device("nonexistent") == []
|
|
|
|
def test_get_targets_referencing_css(self, store):
|
|
store.create_target("T1", "led", color_strip_source_id="css_x")
|
|
store.create_target("T2", "led", color_strip_source_id="css_y")
|
|
names = store.get_targets_referencing_css("css_x")
|
|
assert names == ["T1"]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Persistence
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestOutputTargetPersistence:
|
|
def test_persist_and_reload(self, tmp_path):
|
|
path = str(tmp_path / "ot_persist.json")
|
|
s1 = OutputTargetStore(path)
|
|
t = s1.create_target(
|
|
"Persist", "led",
|
|
device_id="dev_1",
|
|
fps=60,
|
|
tags=["tv"],
|
|
)
|
|
tid = t.id
|
|
|
|
s2 = OutputTargetStore(path)
|
|
loaded = s2.get_target(tid)
|
|
assert loaded.name == "Persist"
|
|
assert isinstance(loaded, WledOutputTarget)
|
|
assert loaded.tags == ["tv"]
|