"""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