test_mode_switch_preserves_assignments_2812.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. """Preserved built-in assignments must stay preserved (#2812).
  2. The mode toggle no longer deletes them. That is only worth anything if the rest
  3. of the app leaves them alone while Spoolman mode is active — otherwise they are
  4. destroyed just as completely, only more slowly.
  5. The auto-unlink pass in ``on_ams_change`` is the one that matters: it ends in
  6. ``db.delete`` for every assignment whose slot no longer matches the fingerprint
  7. it recorded, which is precisely what happens once the user starts loading
  8. different filament under the other mode.
  9. """
  10. from unittest.mock import AsyncMock, MagicMock, patch
  11. import pytest
  12. from httpx import AsyncClient
  13. from sqlalchemy import select
  14. from sqlalchemy.ext.asyncio import AsyncSession
  15. from backend.app.models.settings import Settings
  16. from backend.app.models.spool import Spool
  17. from backend.app.models.spool_assignment import SpoolAssignment
  18. def _status(ams_data):
  19. status = MagicMock()
  20. status.raw_data = {"ams": ams_data, "vt_tray": []}
  21. status.gcode_state = "IDLE"
  22. return status
  23. async def _run_ams_change(printer_id: int, ams_data: list):
  24. from backend.app.main import on_ams_change
  25. status = _status(ams_data)
  26. with (
  27. patch("backend.app.main.printer_manager") as pm_main,
  28. patch("backend.app.services.printer_manager.printer_manager") as pm_inv,
  29. patch("backend.app.main.mqtt_relay") as relay,
  30. patch("backend.app.main.ws_manager") as ws,
  31. ):
  32. pm_main.get_printer.return_value = MagicMock(name="P", serial_number="SER")
  33. pm_main.get_status.return_value = status
  34. pm_main.get_client.return_value = MagicMock()
  35. pm_main.get_model.return_value = "X1C"
  36. pm_inv.get_status.return_value = status
  37. pm_inv.get_client.return_value = MagicMock()
  38. relay.on_ams_change = AsyncMock()
  39. ws.send_printer_status = AsyncMock()
  40. ws.broadcast = AsyncMock()
  41. await on_ams_change(printer_id, ams_data)
  42. class TestAutoUnlinkRespectsTheActiveMode:
  43. @pytest.mark.asyncio
  44. @pytest.mark.integration
  45. async def test_spoolman_mode_does_not_unlink_built_in_assignments(
  46. self, async_client: AsyncClient, printer_factory, db_session: AsyncSession
  47. ):
  48. """The slot now holds something else entirely — a stale fingerprint by
  49. every measure. In built-in mode that is a genuine unlink. In Spoolman
  50. mode these rows are the user's preserved configuration, waiting for
  51. them to switch back, and nothing here is entitled to delete them."""
  52. printer = await printer_factory(name="P1S")
  53. spool = Spool(material="PLA", rgba="FF0000FF")
  54. db_session.add(spool)
  55. await db_session.flush()
  56. db_session.add(Settings(key="spoolman_enabled", value="true"))
  57. db_session.add(
  58. SpoolAssignment(
  59. spool_id=spool.id,
  60. printer_id=printer.id,
  61. ams_id=0,
  62. tray_id=0,
  63. fingerprint_color="FF0000FF",
  64. fingerprint_type="PLA",
  65. )
  66. )
  67. await db_session.commit()
  68. await _run_ams_change(
  69. printer.id,
  70. [{"id": 0, "tray": [{"id": 0, "tray_type": "PETG", "tray_color": "00FF00FF", "state": 11}]}],
  71. )
  72. rows = (
  73. (await db_session.execute(select(SpoolAssignment).where(SpoolAssignment.printer_id == printer.id)))
  74. .scalars()
  75. .all()
  76. )
  77. assert len(rows) == 1
  78. @pytest.mark.asyncio
  79. @pytest.mark.integration
  80. async def test_built_in_mode_still_unlinks_a_stale_assignment(
  81. self, async_client: AsyncClient, printer_factory, db_session: AsyncSession
  82. ):
  83. """The guard must not switch the feature off for the mode that owns it."""
  84. printer = await printer_factory(name="P1S")
  85. spool = Spool(material="PLA", rgba="FF0000FF")
  86. db_session.add(spool)
  87. await db_session.flush()
  88. db_session.add(Settings(key="spoolman_enabled", value="false"))
  89. db_session.add(
  90. SpoolAssignment(
  91. spool_id=spool.id,
  92. printer_id=printer.id,
  93. ams_id=0,
  94. tray_id=0,
  95. fingerprint_color="FF0000FF",
  96. fingerprint_type="PLA",
  97. )
  98. )
  99. await db_session.commit()
  100. await _run_ams_change(
  101. printer.id,
  102. [{"id": 0, "tray": [{"id": 0, "tray_type": "PETG", "tray_color": "00FF00FF", "state": 11}]}],
  103. )
  104. rows = (
  105. (await db_session.execute(select(SpoolAssignment).where(SpoolAssignment.printer_id == printer.id)))
  106. .scalars()
  107. .all()
  108. )
  109. assert rows == []