test_mid_print_spool_swap_routes_3325.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. """Assigning a spool while the printer prints logs the swap (#3325).
  2. Both inventory backends' assignment routes note the new spool and the layer the
  3. printer is on, so completion can split the print between it and the spool the
  4. tray started with. Outside a print nothing is logged.
  5. """
  6. from datetime import datetime
  7. from unittest.mock import AsyncMock, MagicMock, patch
  8. import pytest
  9. from httpx import AsyncClient
  10. from sqlalchemy.ext.asyncio import AsyncSession
  11. from backend.app.models.active_print_session import ActivePrintSession
  12. from backend.app.models.printer import Printer
  13. from backend.app.models.spool import Spool
  14. from backend.app.services import spool_swaps
  15. def _status(layer_num: int, log: list):
  16. status = MagicMock()
  17. status.raw_data = {"ams": {"ams": []}, "vt_tray": [{"id": "254", "tray_type": "PLA"}]}
  18. status.nozzles = [MagicMock(nozzle_diameter="0.4")]
  19. status.ams_extruder_map = None
  20. status.kprofiles = []
  21. status.layer_num = layer_num
  22. status.spool_change_log = log
  23. return status
  24. def _mqtt():
  25. client = MagicMock()
  26. client.ams_set_filament_setting.return_value = True
  27. client.extrusion_cali_sel.return_value = True
  28. return client
  29. async def _printer(db_session, serial: str, printing: bool, start_log: list | None = None) -> Printer:
  30. printer = Printer(name="P2S", serial_number=serial, ip_address="192.168.1.80", access_code="12345678", model="P2S")
  31. db_session.add(printer)
  32. await db_session.commit()
  33. await db_session.refresh(printer)
  34. if printing:
  35. db_session.add(ActivePrintSession(printer_id=printer.id, started_at=datetime.now(), spool_change_log=start_log))
  36. await db_session.commit()
  37. return printer
  38. class TestBuiltInInventory:
  39. async def _assign(self, async_client, db_session, printing: bool):
  40. old = Spool(material="PLA", rgba="000000ff", label_weight=1000, weight_used=0)
  41. new = Spool(material="PLA", rgba="000000ff", label_weight=1000, weight_used=0)
  42. db_session.add_all([old, new])
  43. await db_session.commit()
  44. start = [spool_swaps.entry(spool_swaps.INVENTORY, 255, 0, old.id, 0, start=True)]
  45. printer = await _printer(db_session, f"P2S3325{int(printing)}", printing, start)
  46. status = _status(57, list(start) if printing else [])
  47. with patch("backend.app.services.printer_manager.printer_manager") as pm:
  48. pm.get_client.return_value = _mqtt()
  49. pm.get_status.return_value = status
  50. response = await async_client.post(
  51. "/api/v1/inventory/assignments",
  52. json={"spool_id": new.id, "printer_id": printer.id, "ams_id": 255, "tray_id": 0},
  53. )
  54. assert response.status_code == 200
  55. return printer, status, old, new
  56. @pytest.mark.asyncio
  57. @pytest.mark.integration
  58. async def test_a_swap_mid_print_is_logged(self, async_client: AsyncClient, db_session: AsyncSession):
  59. printer, status, old, new = await self._assign(async_client, db_session, printing=True)
  60. assert spool_swaps.segments(status.spool_change_log, spool_swaps.INVENTORY, 255, 0) == [
  61. (old.id, 0),
  62. (new.id, 57),
  63. ]
  64. row = await db_session.get(ActivePrintSession, printer.id)
  65. await db_session.refresh(row)
  66. assert row.spool_change_log == status.spool_change_log
  67. @pytest.mark.asyncio
  68. @pytest.mark.integration
  69. async def test_no_print_no_log(self, async_client: AsyncClient, db_session: AsyncSession):
  70. _, status, _, _ = await self._assign(async_client, db_session, printing=False)
  71. assert status.spool_change_log == []
  72. class TestSpoolman:
  73. @pytest.fixture
  74. async def settings(self, db_session):
  75. from backend.app.models.settings import Settings
  76. db_session.add(Settings(key="spoolman_enabled", value="true"))
  77. db_session.add(Settings(key="spoolman_url", value="http://localhost:7912"))
  78. await db_session.commit()
  79. @pytest.mark.asyncio
  80. @pytest.mark.integration
  81. async def test_a_swap_mid_print_is_logged(self, async_client: AsyncClient, db_session: AsyncSession, settings):
  82. start = [spool_swaps.entry(spool_swaps.SPOOLMAN, 255, 0, 10, 0, start=True)]
  83. printer = await _printer(db_session, "P2S3325SM", True, start)
  84. status = _status(80, list(start))
  85. spool = {
  86. "id": 11,
  87. "filament": {"id": 1, "name": "Black", "material": "PLA", "color_hex": "000000", "weight": 1000},
  88. "remaining_weight": 1000.0,
  89. "used_weight": 0.0,
  90. "archived": False,
  91. "extra": {},
  92. }
  93. client = MagicMock()
  94. client.has_tag_api = AsyncMock(return_value=False)
  95. client.add_native_tags = AsyncMock(return_value=0)
  96. client.unlink_all_native_tags = AsyncMock()
  97. client.base_url = "http://localhost:7912"
  98. client.health_check = AsyncMock(return_value=True)
  99. client.get_spool = AsyncMock(return_value=spool)
  100. client.get_spools = AsyncMock(return_value=[spool])
  101. client.merge_spool_extra = AsyncMock(return_value=spool)
  102. with (
  103. patch("backend.app.api.routes.spoolman_inventory.printer_manager") as route_pm,
  104. patch("backend.app.services.printer_manager.printer_manager") as pm,
  105. patch("backend.app.api.routes.spoolman_inventory.get_spoolman_client", AsyncMock(return_value=client)),
  106. ):
  107. route_pm.get_client.return_value = _mqtt()
  108. route_pm.get_status.return_value = status
  109. pm.get_status.return_value = status
  110. response = await async_client.post(
  111. "/api/v1/spoolman/inventory/slot-assignments",
  112. json={"spoolman_spool_id": 11, "printer_id": printer.id, "ams_id": 255, "tray_id": 0},
  113. )
  114. assert response.status_code == 200
  115. assert spool_swaps.segments(status.spool_change_log, spool_swaps.SPOOLMAN, 255, 0) == [(10, 0), (11, 80)]