test_ams_remain_sync_while_printing.py 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. """The AMS remain% weight sync stays off while the printer prints.
  2. Usage is deducted precisely at completion from the 3MF; raising weight_used
  3. from remain% during the print as well charges it twice (#880). The guard used
  4. to be only the usage tracker's in-memory session, which a restart with a
  5. stale persisted session never registers.
  6. """
  7. from unittest.mock import AsyncMock, MagicMock, patch
  8. import pytest
  9. from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
  10. from backend.app.models.spool import Spool
  11. from backend.app.models.spool_assignment import SpoolAssignment
  12. from backend.app.services.usage_tracker import _active_sessions
  13. @pytest.fixture(autouse=True)
  14. def _no_sessions():
  15. _active_sessions.clear()
  16. yield
  17. _active_sessions.clear()
  18. async def _weight_after_ams_push(test_engine, db_session, printer_factory, state: str) -> float:
  19. from backend.app.main import on_ams_change
  20. printer = await printer_factory(name="X1C")
  21. spool = Spool(material="PLA", label_weight=1000, weight_used=100)
  22. db_session.add(spool)
  23. await db_session.flush()
  24. db_session.add(
  25. SpoolAssignment(
  26. spool_id=spool.id,
  27. printer_id=printer.id,
  28. ams_id=0,
  29. tray_id=0,
  30. fingerprint_color="FF0000FF",
  31. fingerprint_type="PLA",
  32. )
  33. )
  34. await db_session.commit()
  35. spool_id = spool.id
  36. ams_data = [{"id": 0, "tray": [{"id": 0, "tray_type": "PLA", "tray_color": "FF0000FF", "remain": 50}]}]
  37. status = MagicMock()
  38. status.raw_data = {"ams": {"ams": ams_data}}
  39. status.state = state
  40. status.connected = True
  41. real_db = async_sessionmaker(test_engine, class_=AsyncSession, expire_on_commit=False)
  42. with (
  43. patch("backend.app.main.async_session", real_db),
  44. patch("backend.app.core.database.async_session", real_db),
  45. patch("backend.app.main.printer_manager") as mock_pm,
  46. patch("backend.app.main.mqtt_relay") as mock_relay,
  47. patch("backend.app.main.ws_manager") as mock_ws,
  48. ):
  49. mock_pm.get_printer.return_value = MagicMock(name="X1C", serial_number="REMAINTEST")
  50. mock_pm.get_status.return_value = status
  51. mock_pm.get_client.return_value = MagicMock()
  52. mock_pm.get_model.return_value = "X1C"
  53. mock_relay.on_ams_change = AsyncMock()
  54. mock_ws.send_printer_status = AsyncMock()
  55. mock_ws.broadcast = AsyncMock()
  56. await on_ams_change(printer.id, ams_data)
  57. db_session.expire_all()
  58. return (await db_session.get(Spool, spool_id)).weight_used
  59. @pytest.mark.asyncio
  60. @pytest.mark.integration
  61. @pytest.mark.parametrize("state", ["RUNNING", "PAUSE"])
  62. async def test_no_sync_while_printing_without_a_tracker_session(test_engine, db_session, printer_factory, state):
  63. assert await _weight_after_ams_push(test_engine, db_session, printer_factory, state) == 100
  64. @pytest.mark.asyncio
  65. @pytest.mark.integration
  66. async def test_syncs_when_idle(test_engine, db_session, printer_factory):
  67. assert await _weight_after_ams_push(test_engine, db_session, printer_factory, "IDLE") == 500