| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- """A pre-assigned slot the printer could not be told about is retried (SA-3).
- SpoolBuddy assigns a spool to an empty slot with an empty fingerprint; the
- slot is configured on the printer once filament goes in. The fingerprint was
- recorded even when nothing could be sent (no MQTT client while the printer
- reconnects), and a recorded fingerprint ends the replay, so the slot was never
- configured.
- """
- from unittest.mock import AsyncMock, MagicMock, patch
- import pytest
- from backend.app.models.spool_assignment import SpoolAssignment
- from backend.tests.integration.test_inventory_assign import _make_mock_status, spool_factory # noqa: F401
- pytestmark = [pytest.mark.asyncio, pytest.mark.integration]
- async def _fingerprint_after_insert(db_session, printer_factory, spool_factory, apply: AsyncMock):
- from backend.app.main import on_ams_change
- printer = await printer_factory(name="H2D")
- spool = await spool_factory(slicer_filament="GFL05", material="PLA")
- assignment = SpoolAssignment(spool_id=spool.id, printer_id=printer.id, ams_id=2, tray_id=3)
- db_session.add(assignment)
- await db_session.commit()
- ams_data = [{"id": 2, "tray": [{"id": 3, "tray_type": "PLA", "tray_color": "FF0000FF", "state": 11}]}]
- status = _make_mock_status(ams_data=ams_data)
- with (
- patch("backend.app.api.routes.inventory.apply_spool_to_slot_via_mqtt", apply),
- patch("backend.app.main.printer_manager") as pm,
- patch("backend.app.main.mqtt_relay") as relay,
- patch("backend.app.main.ws_manager") as ws,
- ):
- pm.get_printer.return_value = MagicMock(name="H2D", serial_number="0948BB540200427")
- pm.get_status.return_value = status
- pm.get_client.return_value = MagicMock()
- pm.get_model.return_value = "H2D"
- relay.on_ams_change = AsyncMock()
- ws.send_printer_status = AsyncMock()
- ws.broadcast = AsyncMock()
- await on_ams_change(printer.id, ams_data)
- await db_session.refresh(assignment)
- apply.assert_awaited_once()
- return assignment.fingerprint_type, assignment.fingerprint_color
- async def test_a_slot_the_printer_could_not_be_told_about_is_retried(
- async_client, db_session, printer_factory, spool_factory
- ):
- """No MQTT client (reconnecting): nothing was sent."""
- apply = AsyncMock(return_value=False)
- assert await _fingerprint_after_insert(db_session, printer_factory, spool_factory, apply) == (None, None)
- async def test_a_configuration_that_raised_is_not_retried(async_client, db_session, printer_factory, spool_factory):
- """It may have sent part of the commands, and would raise again on every
- AMS change, mid-print included (remain% steps count as changes)."""
- apply = AsyncMock(side_effect=RuntimeError("preset lookup failed"))
- assert await _fingerprint_after_insert(db_session, printer_factory, spool_factory, apply) == ("PLA", "FF0000FF")
- async def test_a_configured_slot_records_its_fingerprint(async_client, db_session, printer_factory, spool_factory):
- apply = AsyncMock(return_value=True)
- assert await _fingerprint_after_insert(db_session, printer_factory, spool_factory, apply) == ("PLA", "FF0000FF")
- async def _replay(db_session, printer_factory, spool_factory, *, state, tray_now=255, extruder_slots=None):
- """One AMS change for a pre-assigned AMS 2 slot 3 (global tray 11) that now holds filament."""
- from backend.app.main import on_ams_change
- printer = await printer_factory(name="A1 Mini")
- spool = await spool_factory(slicer_filament="GFL99", material="PLA")
- assignment = SpoolAssignment(spool_id=spool.id, printer_id=printer.id, ams_id=2, tray_id=3)
- db_session.add(assignment)
- await db_session.commit()
- ams_data = [{"id": 2, "tray": [{"id": 3, "tray_type": "PLA", "tray_color": "FF0000FF", "state": 11}]}]
- status = _make_mock_status(ams_data=ams_data)
- status.state = state
- status.tray_now = tray_now
- status.extruder_slots = extruder_slots or {}
- apply = AsyncMock(return_value=True)
- with (
- patch("backend.app.api.routes.inventory.apply_spool_to_slot_via_mqtt", apply),
- patch("backend.app.main.printer_manager") as pm,
- patch("backend.app.main.mqtt_relay") as relay,
- patch("backend.app.main.ws_manager") as ws,
- ):
- pm.get_printer.return_value = MagicMock(name="A1 Mini", serial_number="0309CA000000001")
- pm.get_status.return_value = status
- pm.get_client.return_value = MagicMock()
- pm.get_model.return_value = "A1 Mini"
- relay.on_ams_change = AsyncMock()
- ws.send_printer_status = AsyncMock()
- ws.broadcast = AsyncMock()
- await on_ams_change(printer.id, ams_data)
- await db_session.refresh(assignment)
- return apply, assignment
- class TestReplayIntoTheSlotFeedingAPrint:
- """#3434: a spool swapped in after a runout is assigned while the print runs
- from that slot. The printer refuses a setting for it then, and the replay
- sent one per AMS change for the rest of the print."""
- async def test_held_while_the_slot_feeds_the_print(self, async_client, db_session, printer_factory, spool_factory):
- apply, assignment = await _replay(db_session, printer_factory, spool_factory, state="RUNNING", tray_now=11)
- apply.assert_not_awaited()
- # Nothing recorded, so the first AMS change after the print still replays.
- assert assignment.fingerprint_type is None
- async def test_held_for_a_slot_an_extruder_is_fed_from(
- self, async_client, db_session, printer_factory, spool_factory
- ):
- """Dual nozzle: tray_now names the other hotend's slot."""
- from backend.app.services.bambu_mqtt import ExtruderSlot
- slots = {0: ExtruderSlot(ams_id=0, slot_id=0, has_filament=True), 1: ExtruderSlot(2, 3, True)}
- apply, _ = await _replay(
- db_session, printer_factory, spool_factory, state="RUNNING", tray_now=0, extruder_slots=slots
- )
- apply.assert_not_awaited()
- async def test_another_slot_is_configured_mid_print(self, async_client, db_session, printer_factory, spool_factory):
- apply, assignment = await _replay(db_session, printer_factory, spool_factory, state="RUNNING", tray_now=0)
- apply.assert_awaited_once()
- assert assignment.fingerprint_type == "PLA"
- async def test_configured_once_the_print_is_over(self, async_client, db_session, printer_factory, spool_factory):
- apply, assignment = await _replay(db_session, printer_factory, spool_factory, state="FINISH", tray_now=11)
- apply.assert_awaited_once()
- assert assignment.fingerprint_type == "PLA"
|