| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- """An edit made while the scheduler's pass runs is not overwritten (Q2).
- ``check_queue`` loads the pending items once and works on that copy. A queue
- item edited in between (a mapping set by hand, a move to another printer) was
- overwritten when the pass computed and saved its AMS mapping, or dispatched
- to the printer it was on before. The edit lands where the pass waits on
- something; the previous-print check is such a place.
- """
- from unittest.mock import AsyncMock, patch
- import pytest
- from sqlalchemy import update
- from backend.app.models.print_queue import PrintQueueItem
- from backend.app.models.printer import Printer
- from backend.tests.unit.test_scheduler_busy_reasons_3018 import _scheduler, one_printer # noqa: F401
- async def _pinned_item(ctx, ams_mapping: str | None = None) -> int:
- item_id = await ctx.add_item()
- async with ctx.session_maker() as db:
- await db.execute(
- update(PrintQueueItem)
- .where(PrintQueueItem.id == item_id)
- .values(require_previous_success=True, ams_mapping=ams_mapping)
- )
- await db.commit()
- return item_id
- def _edit_during_the_pass(ctx, scheduler, **values):
- async def edit(_db, item, *_args, **_kwargs):
- async with ctx.session_maker() as other:
- await other.execute(update(PrintQueueItem).where(PrintQueueItem.id == item.id).values(**values))
- await other.commit()
- return True
- return patch.object(scheduler, "_check_previous_success", AsyncMock(side_effect=edit))
- @pytest.mark.asyncio
- async def test_a_mapping_set_meanwhile_is_kept(one_printer): # noqa: F811
- """The pass would clear the bogus all-unresolved mapping it loaded."""
- item_id = await _pinned_item(one_printer, ams_mapping="[-1]")
- async with _scheduler(one_printer, idle=True) as scheduler:
- with _edit_during_the_pass(one_printer, scheduler, ams_mapping="[2]"):
- await scheduler.check_queue()
- async with one_printer.session_maker() as db:
- assert (await db.get(PrintQueueItem, item_id)).ams_mapping == "[2]"
- @pytest.mark.asyncio
- async def test_an_item_moved_meanwhile_is_not_sent_to_its_old_printer(one_printer): # noqa: F811
- """Nor given a mapping worked out for its old printer: the pass would
- clear the bogus all-unresolved one it loaded."""
- item_id = await _pinned_item(one_printer, ams_mapping="[-1]")
- async with one_printer.session_maker() as db:
- other = Printer(name="Other", serial_number="OTHER", ip_address="10.0.0.2", access_code="code", model="X1C")
- db.add(other)
- await db.commit()
- other_id = other.id
- async with _scheduler(one_printer, idle=True) as scheduler:
- with _edit_during_the_pass(one_printer, scheduler, printer_id=other_id):
- await scheduler.check_queue()
- assert not scheduler._inflight
- async with one_printer.session_maker() as db:
- item = await db.get(PrintQueueItem, item_id)
- assert (item.status, item.printer_id, item.ams_mapping) == ("pending", other_id, "[-1]")
- @pytest.mark.asyncio
- async def test_the_claim_requires_the_printer_the_item_was_selected_for(one_printer): # noqa: F811
- """A move landing after the re-read: the dispatch's claim still refuses it."""
- from backend.app.services.print_scheduler import PrintScheduler
- item_id = await one_printer.add_item()
- scheduler = PrintScheduler()
- async with one_printer.session_maker() as db:
- assert not await scheduler._claim_for_dispatch(db, item_id, one_printer.printer_id + 1)
- assert await scheduler._claim_for_dispatch(db, item_id, one_printer.printer_id)
- @pytest.mark.asyncio
- async def test_the_power_on_wait_holds_no_transaction(one_printer): # noqa: F811
- """RC-C8: 30-180 s of waiting for a printer to boot with the pass's
- transaction open pins a connection (PostgreSQL) or a stale snapshot."""
- from unittest.mock import MagicMock
- from backend.app.services import print_scheduler as scheduler_module
- from backend.app.services.print_scheduler import PrintScheduler
- scheduler = PrintScheduler()
- order: list[str] = []
- plug = MagicMock(name="plug")
- plug.name = "Plug"
- service = MagicMock(
- get_status=AsyncMock(return_value={"reachable": True, "state": "ON"}), turn_on=AsyncMock(return_value=True)
- )
- async def sleep(_seconds):
- order.append("sleep")
- async with one_printer.session_maker() as db:
- real_commit = db.commit
- async def commit():
- order.append("commit")
- await real_commit()
- with (
- patch.object(scheduler_module.smart_plug_manager, "get_service_for_plug", AsyncMock(return_value=service)),
- patch.object(scheduler_module.asyncio, "sleep", sleep),
- patch.object(scheduler_module.printer_manager, "connect_printer", AsyncMock(return_value=True)),
- patch.object(db, "commit", commit),
- ):
- assert await scheduler._power_on_and_wait(plug, one_printer.printer_id, db)
- assert order[:2] == ["commit", "sleep"]
- @pytest.mark.asyncio
- async def test_an_item_cancelled_meanwhile_is_left_as_cancelled(one_printer): # noqa: F811
- item_id = await _pinned_item(one_printer, ams_mapping="[-1]")
- async with _scheduler(one_printer, idle=True) as scheduler:
- with _edit_during_the_pass(one_printer, scheduler, status="cancelled"):
- await scheduler.check_queue()
- async with one_printer.session_maker() as db:
- item = await db.get(PrintQueueItem, item_id)
- assert (item.status, item.ams_mapping) == ("cancelled", "[-1]")
- @pytest.mark.asyncio
- async def test_another_plate_chosen_meanwhile_is_not_mapped_from_the_old_one(one_printer): # noqa: F811
- """The new mapping belongs to the new plate; checked against the old
- plate's filaments it would be thrown away."""
- item_id = await _pinned_item(one_printer, ams_mapping="[-1]")
- async with _scheduler(one_printer, idle=True) as scheduler:
- with _edit_during_the_pass(one_printer, scheduler, plate_id=2, ams_mapping="[3]"):
- await scheduler.check_queue()
- assert not scheduler._inflight
- async with one_printer.session_maker() as db:
- item = await db.get(PrintQueueItem, item_id)
- assert (item.plate_id, item.ams_mapping) == (2, "[3]")
- @pytest.mark.asyncio
- async def test_a_model_job_pinned_meanwhile_is_not_sent_to_the_printer_it_was_offered(one_printer): # noqa: F811
- item_id = await _pinned_item(one_printer)
- async with one_printer.session_maker() as db:
- other = Printer(name="Other", serial_number="OTHER", ip_address="10.0.0.2", access_code="code", model="X1C")
- db.add(other)
- await db.commit()
- other_id = other.id
- await db.execute(
- update(PrintQueueItem).where(PrintQueueItem.id == item_id).values(printer_id=None, target_model="X1C")
- )
- await db.commit()
- async with _scheduler(one_printer, idle=True) as scheduler:
- with _edit_during_the_pass(one_printer, scheduler, printer_id=other_id, target_model=None):
- await scheduler.check_queue()
- assert not scheduler._inflight
- async with one_printer.session_maker() as db:
- item = await db.get(PrintQueueItem, item_id)
- assert (item.status, item.printer_id, item.target_model) == ("pending", other_id, None)
- @pytest.mark.asyncio
- @pytest.mark.parametrize(
- "edit",
- [
- {"plate_id": 2},
- {"filament_overrides": '[{"slot_id": 1, "type": "PETG"}]'},
- {"skip_filament_check": True},
- {"target_model": "P1S"},
- {"target_location": "Lab"},
- ],
- )
- async def test_any_edit_to_what_the_dispatch_is_worked_out_from_waits_a_round(one_printer, edit): # noqa: F811
- await _pinned_item(one_printer)
- async with _scheduler(one_printer, idle=True) as scheduler:
- with _edit_during_the_pass(one_printer, scheduler, **edit):
- await scheduler.check_queue()
- assert not scheduler._inflight
|