diff --git a/backend/app/api/routes/print_queue.py b/backend/app/api/routes/print_queue.py index ed20ee092..a085c3595 100644 --- a/backend/app/api/routes/print_queue.py +++ b/backend/app/api/routes/print_queue.py @@ -616,7 +616,12 @@ async def list_queue( # Cross-model candidates (#671) and their files, for the card label. selectinload(PrintQueueItem.variants).selectinload(PrintQueueVariant.library_file), ) - .order_by(PrintQueueItem.printer_id.nulls_first(), PrintQueueItem.position) + # The order the scheduler dispatches in (#3200), so the first pending + # item for a printer is the one it will start next -- which is what the + # printer card's "Next in queue" shows. Sorting by printer first put + # every "Any " job (no printer_id) ahead of a job pinned to that + # printer, whatever their positions. + .order_by(PrintQueueItem.position, PrintQueueItem.id) ) if user is not None and not can_read_all: query = query.where(PrintQueueItem.created_by_id == user.id) diff --git a/backend/tests/integration/test_print_queue_api.py b/backend/tests/integration/test_print_queue_api.py index 79e547aa2..f756c27c1 100644 --- a/backend/tests/integration/test_print_queue_api.py +++ b/backend/tests/integration/test_print_queue_api.py @@ -119,6 +119,35 @@ class TestPrintQueueAPI: assert response.status_code == 200 assert isinstance(response.json(), list) + @pytest.mark.asyncio + @pytest.mark.integration + @pytest.mark.parametrize("pinned_first", [True, False], ids=["pinned-above-any", "any-above-pinned"]) + async def test_list_follows_queue_position_across_pinned_and_any_jobs( + self, async_client: AsyncClient, printer_factory, queue_item_factory, pinned_first + ): + """A printer's first pending item is the one the scheduler starts next (#3200). + + The printer card's "Next in queue" shows that first item. The list used + to sort by printer first, which put every "Any " job (no + printer_id) ahead of a job pinned to the printer -- the order the + scheduler itself had until #3200 -- so the card kept naming a lower + "Any" job after dispatch was fixed. + """ + printer = await printer_factory(model="P2S") + pinned_pos, any_pos = (1, 2) if pinned_first else (2, 1) + pinned = await queue_item_factory(printer_id=printer.id, position=pinned_pos) + any_model = await queue_item_factory(printer_id=None, target_model="P2S", position=any_pos) + later_any = await queue_item_factory(printer_id=None, target_model="P2S", position=3) + + response = await async_client.get( + "/api/v1/queue/", params={"printer_id": printer.id, "status": "pending", "target_model": "P2S"} + ) + + assert response.status_code == 200 + ids = [item["id"] for item in response.json()] + expected_head = [pinned.id, any_model.id] if pinned_first else [any_model.id, pinned.id] + assert ids == [*expected_head, later_any.id] + @pytest.mark.asyncio @pytest.mark.integration async def test_add_to_queue(self, async_client: AsyncClient, printer_factory, archive_factory, db_session):