fix: tolerate legacy NULL properties on pending devices
The pending_devices.properties column is added by an idempotent migration, so existing rows have properties = NULL. PendingDeviceResponse typed it as a list, so GET /scan/pending 500'd on any pre-existing device. - Coerce NULL/non-list properties to [] in PendingDeviceResponse. - Backfill existing NULL rows to '[]' in init_db migrations. - Regression test: /scan/pending returns 200 with a legacy NULL-properties row. ha-relevant: maybe
This commit is contained in:
@@ -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) ---
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
||||
|
||||
@@ -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")], [])
|
||||
|
||||
Reference in New Issue
Block a user