"""Printer locations (groups) API (#2962). Locations live on printers (``printers.location``) and, for appearance and empty locations, in ``printer_locations``. Every write is one server-side transaction: the page used to send one PATCH per printer from its cached list, so a failure halfway split a location, and another user's move was undone. """ import pytest from httpx import AsyncClient from sqlalchemy import inspect, select from backend.app.models.print_queue import PrintQueueItem from backend.app.models.printer import Printer from backend.app.models.printer_location import PrinterLocation BASE = "/api/v1/printer-locations/" async def _locations(client: AsyncClient) -> dict[str, dict]: response = await client.get(BASE) assert response.status_code == 200 return {loc["name"]: loc for loc in response.json()} async def _printer_location(db_session, printer) -> str | None: # A column select reads the row, not the identity map, so nothing needs # expiring; the id comes from the identity, which never lazy-loads. printer_id = inspect(printer).identity[0] return (await db_session.execute(select(Printer.location).where(Printer.id == printer_id))).scalar_one() @pytest.mark.asyncio @pytest.mark.integration class TestList: async def test_union_of_rows_and_printer_locations(self, async_client, printer_factory, db_session): await printer_factory(name="A", location="Workshop") await printer_factory(name="B", location="Workshop") await printer_factory(name="C", location="") await printer_factory(name="D", location=None) db_session.add(PrinterLocation(name="Future rack", icon="home", color="#3b82f6")) await db_session.commit() locations = await _locations(async_client) assert set(locations) == {"Workshop", "Future rack"} assert locations["Workshop"]["printer_count"] == 2 assert locations["Workshop"]["id"] is None assert locations["Future rack"] == { "id": locations["Future rack"]["id"], "name": "Future rack", "icon": "home", "color": "#3b82f6", "printer_count": 0, } async def test_natural_order(self, async_client, db_session): for name in ("Rack 10", "Rack 2", "Office"): db_session.add(PrinterLocation(name=name)) await db_session.commit() response = await async_client.get(BASE) assert [loc["name"] for loc in response.json()] == ["Office", "Rack 2", "Rack 10"] @pytest.mark.asyncio @pytest.mark.integration class TestCreate: async def test_an_empty_location_persists(self, async_client): response = await async_client.post(BASE, json={"name": " Basement ", "icon": "home", "color": "#ef4444"}) assert response.status_code == 201 assert response.json()["name"] == "Basement" assert "Basement" in await _locations(async_client) async def test_a_location_printers_already_use_gets_a_row(self, async_client, printer_factory): await printer_factory(location="Workshop") response = await async_client.post(BASE, json={"name": "Workshop", "color": "#22c55e"}) assert response.status_code == 201 assert response.json()["printer_count"] == 1 assert response.json()["color"] == "#22c55e" @pytest.mark.parametrize("name", ["Basement", "basement", "BASEMENT"]) async def test_a_duplicate_is_refused_ignoring_case(self, async_client, name): await async_client.post(BASE, json={"name": "Basement"}) response = await async_client.post(BASE, json={"name": name}) assert response.status_code == 409 @pytest.mark.parametrize( "body", [ {"name": ""}, {"name": " "}, {"name": "x" * 101}, {"name": "Ok", "color": "red"}, {"name": "Ok", "icon": "