test_spoolman_manual_sync_empty_slots.py 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. """The manual Spoolman sync and slots that read empty.
  2. The automatic pass unlinks a Spoolman slot only when it reads empty while the
  3. printer is not printing and the presence bit does not say a spool is there.
  4. The Sync buttons in Settings deleted the row whenever the tray had no type or
  5. colour: mid-print (a runout reads empty), or a non-RFID spool whose tray type
  6. was never set.
  7. """
  8. from types import SimpleNamespace
  9. from unittest.mock import AsyncMock, MagicMock, patch
  10. import pytest
  11. from httpx import AsyncClient
  12. from sqlalchemy import select
  13. from backend.app.models.settings import Settings
  14. from backend.app.models.spoolman_slot_assignment import SpoolmanSlotAssignment
  15. @pytest.fixture
  16. async def printer_id(db_session, printer_factory):
  17. db_session.add(Settings(key="spoolman_enabled", value="true"))
  18. db_session.add(Settings(key="spoolman_url", value="http://localhost:7912"))
  19. printer = await printer_factory()
  20. db_session.add(SpoolmanSlotAssignment(printer_id=printer.id, ams_id=0, tray_id=0, spoolman_spool_id=42))
  21. await db_session.commit()
  22. return printer.id
  23. async def _sync(async_client, printer_id, path, gcode_state, exists):
  24. tray = {"id": 0, "tray_type": "", "tray_color": ""}
  25. if exists is not None:
  26. tray["exists"] = exists
  27. state = SimpleNamespace(state=gcode_state, raw_data={"ams": [{"id": 0, "tray": [tray]}]})
  28. client = MagicMock()
  29. client.health_check = AsyncMock(return_value=True)
  30. client.get_spools = AsyncMock(return_value=[])
  31. client.parse_ams_tray = MagicMock(return_value=None)
  32. with (
  33. patch("backend.app.api.routes.spoolman.get_spoolman_client", AsyncMock(return_value=client)),
  34. patch("backend.app.api.routes.spoolman.printer_manager") as pm,
  35. ):
  36. pm.get_status = MagicMock(return_value=state)
  37. response = await async_client.post(path.format(printer_id=printer_id))
  38. assert response.status_code == 200, response.text
  39. PATHS = ["/api/v1/spoolman/sync/{printer_id}", "/api/v1/spoolman/sync-all"]
  40. @pytest.mark.asyncio
  41. @pytest.mark.integration
  42. @pytest.mark.parametrize("path", PATHS)
  43. @pytest.mark.parametrize("gcode_state", ["RUNNING", "PAUSE"])
  44. async def test_a_slot_reading_empty_mid_print_keeps_its_spool(
  45. async_client: AsyncClient, db_session, printer_id, path, gcode_state
  46. ):
  47. await _sync(async_client, printer_id, path, gcode_state, exists=None)
  48. rows = (await db_session.execute(select(SpoolmanSlotAssignment))).scalars().all()
  49. assert len(rows) == 1
  50. @pytest.mark.asyncio
  51. @pytest.mark.integration
  52. @pytest.mark.parametrize("path", PATHS)
  53. async def test_a_slot_the_presence_bit_calls_occupied_keeps_its_spool(
  54. async_client: AsyncClient, db_session, printer_id, path
  55. ):
  56. await _sync(async_client, printer_id, path, "IDLE", exists=True)
  57. rows = (await db_session.execute(select(SpoolmanSlotAssignment))).scalars().all()
  58. assert len(rows) == 1
  59. @pytest.mark.asyncio
  60. @pytest.mark.integration
  61. @pytest.mark.parametrize("path", PATHS)
  62. async def test_an_empty_slot_of_an_idle_printer_is_unlinked(async_client: AsyncClient, db_session, printer_id, path):
  63. await _sync(async_client, printer_id, path, "IDLE", exists=False)
  64. rows = (await db_session.execute(select(SpoolmanSlotAssignment))).scalars().all()
  65. assert rows == []