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

125 lines
4.4 KiB
Python

"""E2E: Output target lifecycle.
Tests target CRUD with a dependency on a device:
create device -> create target -> list -> update -> delete target -> cleanup device.
"""
import pytest
class TestOutputTargetLifecycle:
"""A user wires up an output target to a device."""
def _create_device(self, client) -> str:
"""Helper: create a mock device and return its ID."""
resp = client.post("/api/v1/devices", json={
"name": "Target Test Device",
"url": "mock://target-test",
"device_type": "mock",
"led_count": 60,
})
assert resp.status_code == 201
return resp.json()["id"]
def test_full_target_crud_lifecycle(self, client):
device_id = self._create_device(client)
# 1. List targets -- should be empty
resp = client.get("/api/v1/output-targets")
assert resp.status_code == 200
assert resp.json()["count"] == 0
# 2. Create an output target referencing the device
create_payload = {
"name": "E2E Test Target",
"target_type": "led",
"device_id": device_id,
"fps": 30,
"protocol": "ddp",
"tags": ["e2e"],
}
resp = client.post("/api/v1/output-targets", json=create_payload)
assert resp.status_code == 201, f"Create failed: {resp.text}"
target = resp.json()
target_id = target["id"]
assert target["name"] == "E2E Test Target"
assert target["device_id"] == device_id
assert target["target_type"] == "led"
assert target["fps"] == 30
assert target["protocol"] == "ddp"
# 3. List targets -- should contain the new target
resp = client.get("/api/v1/output-targets")
assert resp.status_code == 200
data = resp.json()
assert data["count"] == 1
assert data["targets"][0]["id"] == target_id
# 4. Get target by ID
resp = client.get(f"/api/v1/output-targets/{target_id}")
assert resp.status_code == 200
assert resp.json()["name"] == "E2E Test Target"
# 5. Update the target -- change name and fps
resp = client.put(
f"/api/v1/output-targets/{target_id}",
json={"name": "Updated Target", "fps": 60},
)
assert resp.status_code == 200
updated = resp.json()
assert updated["name"] == "Updated Target"
assert updated["fps"] == 60
# 6. Verify update via GET
resp = client.get(f"/api/v1/output-targets/{target_id}")
assert resp.status_code == 200
assert resp.json()["name"] == "Updated Target"
# 7. Delete the target
resp = client.delete(f"/api/v1/output-targets/{target_id}")
assert resp.status_code == 204
# 8. Verify target is gone
resp = client.get(f"/api/v1/output-targets/{target_id}")
assert resp.status_code == 404
# 9. Clean up the device
resp = client.delete(f"/api/v1/devices/{device_id}")
assert resp.status_code == 204
def test_cannot_delete_device_referenced_by_target(self, client):
"""Deleting a device that has a target should return 409."""
device_id = self._create_device(client)
resp = client.post("/api/v1/output-targets", json={
"name": "Blocking Target",
"target_type": "led",
"device_id": device_id,
})
assert resp.status_code == 201
target_id = resp.json()["id"]
# Attempt to delete device -- should fail
resp = client.delete(f"/api/v1/devices/{device_id}")
assert resp.status_code == 409
assert "referenced" in resp.json()["detail"].lower()
# Clean up: delete target first, then device
resp = client.delete(f"/api/v1/output-targets/{target_id}")
assert resp.status_code == 204
resp = client.delete(f"/api/v1/devices/{device_id}")
assert resp.status_code == 204
def test_create_target_with_invalid_device_returns_422(self, client):
"""Creating a target with a non-existent device_id returns 422."""
resp = client.post("/api/v1/output-targets", json={
"name": "Orphan Target",
"target_type": "led",
"device_id": "nonexistent_device",
})
assert resp.status_code == 422
def test_get_nonexistent_target_returns_404(self, client):
resp = client.get("/api/v1/output-targets/nonexistent_id")
assert resp.status_code == 404