test_start_print_busy_guard_2598.py 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
  1. """start_print() must not publish project_file to a busy printer (#2598).
  2. The firmware rejects a start command while the printer is not idle with
  3. 0500_4004 ("Device is busy and cannot start a new task"), and on an A1 mini
  4. that error cancels the RUNNING job. Because every dispatch path (queue
  5. scheduler, manual start, webhook, Virtual-Printer forward) funnels through
  6. BambuMQTTClient.start_print, a run-state guard here covers them all.
  7. IDLE / FINISH / FAILED are valid start targets; only PREPARE / SLICING /
  8. RUNNING / PAUSE are refused.
  9. """
  10. import json
  11. from unittest.mock import MagicMock
  12. import pytest
  13. from backend.app.services.bambu_mqtt import BambuMQTTClient
  14. def _connected_client() -> BambuMQTTClient:
  15. client = BambuMQTTClient(ip_address="127.0.0.1", serial_number="TEST123", access_code="12345678")
  16. client._client = MagicMock()
  17. client.state.connected = True
  18. return client
  19. @pytest.mark.parametrize("busy_state", ["RUNNING", "PREPARE", "PAUSE", "SLICING"])
  20. def test_start_print_refused_when_printer_busy(busy_state):
  21. client = _connected_client()
  22. client.state.state = busy_state
  23. result = client.start_print("job.3mf")
  24. assert result is False, f"start_print should refuse while {busy_state}"
  25. client._client.publish.assert_not_called()
  26. @pytest.mark.parametrize("idle_state", ["IDLE", "FINISH", "FAILED"])
  27. def test_start_print_publishes_when_printer_idle(idle_state):
  28. client = _connected_client()
  29. client.state.state = idle_state
  30. result = client.start_print("job.3mf")
  31. assert result is True, f"start_print should proceed while {idle_state}"
  32. client._client.publish.assert_called_once()
  33. topic, payload = client._client.publish.call_args.args[:2]
  34. assert json.loads(payload)["print"]["command"] == "project_file"
  35. def test_busy_guard_takes_precedence_over_disconnected():
  36. """A busy printer is refused even if the connection flag is stale/false —
  37. the guard runs before the connection check, so no publish is attempted."""
  38. client = _connected_client()
  39. client.state.connected = False
  40. client.state.state = "RUNNING"
  41. assert client.start_print("job.3mf") is False
  42. client._client.publish.assert_not_called()