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

@@ -0,0 +1,160 @@
"""Tests for SyncClockStore — CRUD, speed clamping, name uniqueness."""
import pytest
from wled_controller.storage.sync_clock import SyncClock
from wled_controller.storage.sync_clock_store import SyncClockStore
@pytest.fixture
def store(tmp_path) -> SyncClockStore:
return SyncClockStore(str(tmp_path / "sync_clocks.json"))
# ---------------------------------------------------------------------------
# SyncClock model
# ---------------------------------------------------------------------------
class TestSyncClockModel:
def test_to_dict_round_trip(self, make_sync_clock):
clock = make_sync_clock(name="RT", speed=2.5, description="test", tags=["a"])
data = clock.to_dict()
restored = SyncClock.from_dict(data)
assert restored.id == clock.id
assert restored.name == "RT"
assert restored.speed == 2.5
assert restored.description == "test"
assert restored.tags == ["a"]
def test_from_dict_defaults(self):
data = {
"id": "sc_1",
"name": "Default",
"created_at": "2025-01-01T00:00:00+00:00",
"updated_at": "2025-01-01T00:00:00+00:00",
}
clock = SyncClock.from_dict(data)
assert clock.speed == 1.0
assert clock.description is None
assert clock.tags == []
# ---------------------------------------------------------------------------
# SyncClockStore CRUD
# ---------------------------------------------------------------------------
class TestSyncClockStoreCRUD:
def test_create_clock(self, store):
c = store.create_clock(name="Clock A")
assert c.id.startswith("sc_")
assert c.name == "Clock A"
assert c.speed == 1.0
assert store.count() == 1
def test_create_clock_with_options(self, store):
c = store.create_clock(
name="Fast", speed=5.0, description="speedy", tags=["anim"]
)
assert c.speed == 5.0
assert c.description == "speedy"
assert c.tags == ["anim"]
def test_get_clock(self, store):
created = store.create_clock(name="Get")
got = store.get_clock(created.id)
assert got.name == "Get"
def test_get_all_clocks(self, store):
store.create_clock("A")
store.create_clock("B")
assert len(store.get_all_clocks()) == 2
def test_delete_clock(self, store):
c = store.create_clock("Del")
store.delete_clock(c.id)
assert store.count() == 0
def test_delete_not_found(self, store):
with pytest.raises(ValueError, match="not found"):
store.delete_clock("nope")
def test_update_clock(self, store):
c = store.create_clock(name="Old", speed=1.0)
updated = store.update_clock(c.id, name="New", speed=3.0)
assert updated.name == "New"
assert updated.speed == 3.0
def test_update_clock_partial(self, store):
c = store.create_clock(name="Keep", speed=2.0, description="orig")
updated = store.update_clock(c.id, speed=4.0)
assert updated.name == "Keep"
assert updated.speed == 4.0
assert updated.description == "orig"
# ---------------------------------------------------------------------------
# Speed clamping
# ---------------------------------------------------------------------------
class TestSpeedClamping:
def test_create_clamps_low(self, store):
c = store.create_clock(name="Low", speed=0.01)
assert c.speed == 0.1
def test_create_clamps_high(self, store):
c = store.create_clock(name="High", speed=100.0)
assert c.speed == 10.0
def test_update_clamps_low(self, store):
c = store.create_clock(name="UL", speed=1.0)
updated = store.update_clock(c.id, speed=-5.0)
assert updated.speed == 0.1
def test_update_clamps_high(self, store):
c = store.create_clock(name="UH", speed=1.0)
updated = store.update_clock(c.id, speed=999.0)
assert updated.speed == 10.0
# ---------------------------------------------------------------------------
# Name uniqueness
# ---------------------------------------------------------------------------
class TestSyncClockNameUniqueness:
def test_duplicate_name_create(self, store):
store.create_clock("Same")
with pytest.raises(ValueError, match="already exists"):
store.create_clock("Same")
def test_duplicate_name_update(self, store):
store.create_clock("First")
c2 = store.create_clock("Second")
with pytest.raises(ValueError, match="already exists"):
store.update_clock(c2.id, name="First")
def test_rename_to_own_name_ok(self, store):
c = store.create_clock("Self")
updated = store.update_clock(c.id, name="Self", speed=9.0)
assert updated.speed == 9.0
# ---------------------------------------------------------------------------
# Persistence
# ---------------------------------------------------------------------------
class TestSyncClockPersistence:
def test_persist_and_reload(self, tmp_path):
path = str(tmp_path / "sc_persist.json")
s1 = SyncClockStore(path)
c = s1.create_clock(name="Persist", speed=2.5)
cid = c.id
s2 = SyncClockStore(path)
loaded = s2.get_clock(cid)
assert loaded.name == "Persist"
assert loaded.speed == 2.5