diff --git a/backend/app/db/database.py b/backend/app/db/database.py index 76470d6..e7177b8 100644 --- a/backend/app/db/database.py +++ b/backend/app/db/database.py @@ -117,6 +117,8 @@ async def init_db() -> None: await conn.exec_driver_sql("ALTER TABLE pending_devices ADD COLUMN discovery_source TEXT") with suppress(OperationalError): await conn.exec_driver_sql("ALTER TABLE pending_devices ADD COLUMN properties JSON") + with suppress(OperationalError): + await conn.exec_driver_sql("UPDATE pending_devices SET properties = '[]' WHERE properties IS NULL") with suppress(OperationalError): await conn.exec_driver_sql("ALTER TABLE scan_runs ADD COLUMN kind TEXT NOT NULL DEFAULT 'ip'") # --- Zigbee schema migrations (logged variant per CLAUDE.md feedback) --- diff --git a/backend/app/schemas/scan.py b/backend/app/schemas/scan.py index d64bd79..0b0827c 100644 --- a/backend/app/schemas/scan.py +++ b/backend/app/schemas/scan.py @@ -1,7 +1,7 @@ from datetime import datetime from typing import Any -from pydantic import BaseModel +from pydantic import BaseModel, field_validator class PendingDeviceResponse(BaseModel): @@ -35,6 +35,12 @@ class PendingDeviceResponse(BaseModel): node_last_modified: datetime | None = None node_last_seen: datetime | None = None + @field_validator("properties", mode="before") + @classmethod + def _coerce_properties(cls, v: Any) -> list[Any]: + # Legacy rows (column added by migration) have properties = NULL. + return v if isinstance(v, list) else [] + model_config = {"from_attributes": True} diff --git a/backend/tests/test_proxmox_router.py b/backend/tests/test_proxmox_router.py index 2303430..115d03d 100644 --- a/backend/tests/test_proxmox_router.py +++ b/backend/tests/test_proxmox_router.py @@ -178,6 +178,21 @@ async def test_persist_keeps_hidden_hidden(db_session) -> None: assert row.status == "hidden" +@pytest.mark.asyncio +async def test_pending_endpoint_tolerates_legacy_null_properties(client: AsyncClient, headers: dict, db_session) -> None: + # Legacy row: properties column NULL (added by migration on older DBs). + dev = PendingDevice( + id=str(uuid.uuid4()), ip="192.168.1.9", suggested_type="server", + status="pending", discovery_source="arp", + ) + dev.properties = None + db_session.add(dev) + await db_session.commit() + res = await client.get("/api/v1/scan/pending", headers=headers) + assert res.status_code == 200 + assert res.json()[0]["properties"] == [] + + @pytest.mark.asyncio async def test_persist_never_deletes(db_session) -> None: await _persist_pending_import(db_session, [_guest_node(101, "10.0.0.5")], [])