test_drying_screen_only.py 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. """P1-series AMS drying is screen-only — the API must refuse it (#2533).
  2. Bambu's P1 manual states that "P1S connected AMS drying functions may only be
  3. controlled from the P1S screen". The firmware still answers
  4. ``ams_filament_drying`` with ``result: success`` and then ignores it, which is
  5. exactly what the reporter saw: three commands accepted on an idle P1S with an
  6. AMS 2 Pro, and the unit never left ``dry_status: 0``.
  7. So a command we can't fulfil must be refused rather than acked, and that has to
  8. hold for stop as well as start — a cycle a P1S user started at the printer can
  9. only be ended there.
  10. """
  11. from unittest.mock import MagicMock, patch
  12. import pytest
  13. from httpx import AsyncClient
  14. @pytest.fixture
  15. def mqtt_send():
  16. """Watch the MQTT command so we can assert nothing was published."""
  17. with patch(
  18. "backend.app.services.printer_manager.printer_manager.send_drying_command",
  19. new=MagicMock(return_value=True),
  20. ) as m:
  21. yield m
  22. @pytest.fixture
  23. def live_state():
  24. """A connected printer on firmware new enough that only the model gates drying."""
  25. state = MagicMock()
  26. state.firmware_version = "01.10.00.00"
  27. state.raw_data = {"ams": [{"id": 0, "module_type": "n3f", "tray": []}]}
  28. with patch(
  29. "backend.app.services.printer_manager.printer_manager.get_status",
  30. new=MagicMock(return_value=state),
  31. ) as m:
  32. yield m
  33. @pytest.mark.asyncio
  34. @pytest.mark.integration
  35. @pytest.mark.parametrize("model", ["P1S", "P1P"])
  36. @pytest.mark.parametrize("action", ["start", "stop"])
  37. async def test_screen_only_model_refuses_drying(
  38. async_client: AsyncClient, printer_factory, mqtt_send, live_state, model, action
  39. ):
  40. printer = await printer_factory(model=model)
  41. response = await async_client.post(f"/api/v1/printers/{printer.id}/drying/{action}?ams_id=0")
  42. assert response.status_code == 400
  43. assert "screen" in response.json()["detail"].lower()
  44. # And nothing went out on the wire — an ack the printer would drop is worse
  45. # than a refusal, because it leaves the user believing drying is running.
  46. mqtt_send.assert_not_called()
  47. @pytest.mark.asyncio
  48. @pytest.mark.integration
  49. @pytest.mark.parametrize("action", ["start", "stop"])
  50. async def test_commandable_model_still_dries(async_client: AsyncClient, printer_factory, mqtt_send, live_state, action):
  51. printer = await printer_factory(model="X1C")
  52. response = await async_client.post(f"/api/v1/printers/{printer.id}/drying/{action}?ams_id=0")
  53. assert response.status_code == 200
  54. mqtt_send.assert_called_once()