fix: bulk-approve places every selected device onto the active canvas

Bulk-approve filtered status=='pending', so a device already approved onto
another canvas (status is global, canvas membership is per-design) — or whose
node was later deleted — was silently skipped. Selecting 64 devices on an
empty canvas produced only the ~28 still pending.

Approve now places a node on the target design for any selected, non-hidden
device that isn't already on that design (deduped by ip/ieee_address, including
within the batch). Returned device_ids/node_ids stay index-aligned so the
client places them all.

ha-relevant: yes
This commit is contained in:
Pouzor
2026-06-27 19:02:28 +02:00
parent 3020b400ed
commit c8d25c2383
2 changed files with 103 additions and 3 deletions
+69
View File
@@ -792,6 +792,75 @@ async def test_bulk_approve_approves_devices(client: AsyncClient, headers, two_p
assert all(d["status"] == "approved" for d in inventory)
@pytest.mark.asyncio
async def test_bulk_approve_places_already_approved_device_on_another_design(
client: AsyncClient, headers, db_session, two_pending_devices
):
"""Regression: a device already approved (status='approved', e.g. placed on
another canvas) must still get a node on the design being approved onto.
Previously bulk-approve filtered status=='pending', so selecting an
already-approved device created no node — the user saw fewer nodes than
they selected."""
ids = [d.id for d in two_pending_devices]
design_a = await _add_design(db_session, "Canvas A")
design_b = await _add_design(db_session, "Canvas B")
# Approve both onto design A.
res_a = await client.post(
"/api/v1/scan/pending/bulk-approve",
json={"device_ids": ids, "design_id": design_a},
headers=headers,
)
assert res_a.json()["approved"] == 2
# Re-approve the same (now status='approved') devices onto design B.
res_b = await client.post(
"/api/v1/scan/pending/bulk-approve",
json={"device_ids": ids, "design_id": design_b},
headers=headers,
)
data_b = res_b.json()
assert data_b["approved"] == 2, "already-approved devices must place onto the new canvas"
assert data_b["skipped"] == 0
# Two nodes now exist on each design.
from app.db.models import Node as NodeModel
nodes_b = (
await db_session.execute(select(NodeModel).where(NodeModel.design_id == design_b))
).scalars().all()
assert len(nodes_b) == 2
@pytest.mark.asyncio
async def test_bulk_approve_skips_device_already_on_target_design(
client: AsyncClient, headers, db_session, two_pending_devices
):
"""A device already on the target canvas (same ip) is not placed twice."""
ids = [d.id for d in two_pending_devices]
design = await _add_design(db_session, "Canvas")
# First device already sits on the canvas (matched by ip).
db_session.add(_node(design, ip="192.168.1.10"))
await db_session.commit()
res = await client.post(
"/api/v1/scan/pending/bulk-approve",
json={"device_ids": ids, "design_id": design},
headers=headers,
)
data = res.json()
assert data["approved"] == 1 # only the second device (192.168.1.11)
assert data["skipped"] == 1
from app.db.models import Node as NodeModel
nodes = (
await db_session.execute(select(NodeModel).where(NodeModel.design_id == design))
).scalars().all()
# The pre-existing node plus the one newly approved — no duplicate for .10.
assert len(nodes) == 2
assert sorted(n.ip for n in nodes) == ["192.168.1.10", "192.168.1.11"]
@pytest.fixture
async def zigbee_pending_device(db_session):
device = PendingDevice(