| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899 |
- """The AMS remain% weight sync stays off while the printer prints.
- Usage is deducted precisely at completion from the 3MF; raising weight_used
- from remain% during the print as well charges it twice (#880). The guard used
- to be only the usage tracker's in-memory session, which a restart with a
- stale persisted session never registers.
- """
- from unittest.mock import AsyncMock, MagicMock, patch
- import pytest
- from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
- from backend.app.models.spool import Spool
- from backend.app.models.spool_assignment import SpoolAssignment
- from backend.app.services.usage_tracker import _active_sessions
- @pytest.fixture(autouse=True)
- def _no_sessions():
- _active_sessions.clear()
- yield
- _active_sessions.clear()
- async def _weight_after_ams_push(
- test_engine, db_session, printer_factory, state: str, sent: list[str] | None = None
- ) -> float:
- from backend.app.main import on_ams_change
- printer = await printer_factory(name="X1C")
- spool = Spool(material="PLA", label_weight=1000, weight_used=100)
- db_session.add(spool)
- await db_session.flush()
- db_session.add(
- SpoolAssignment(
- spool_id=spool.id,
- printer_id=printer.id,
- ams_id=0,
- tray_id=0,
- fingerprint_color="FF0000FF",
- fingerprint_type="PLA",
- )
- )
- await db_session.commit()
- spool_id = spool.id
- ams_data = [{"id": 0, "tray": [{"id": 0, "tray_type": "PLA", "tray_color": "FF0000FF", "remain": 50}]}]
- status = MagicMock()
- status.raw_data = {"ams": {"ams": ams_data}}
- status.state = state
- status.connected = True
- real_db = async_sessionmaker(test_engine, class_=AsyncSession, expire_on_commit=False)
- with (
- patch("backend.app.main.async_session", real_db),
- patch("backend.app.core.database.async_session", real_db),
- patch("backend.app.main.printer_manager") as mock_pm,
- patch("backend.app.main.mqtt_relay") as mock_relay,
- patch("backend.app.main.ws_manager") as mock_ws,
- ):
- mock_pm.get_printer.return_value = MagicMock(name="X1C", serial_number="REMAINTEST")
- mock_pm.get_status.return_value = status
- mock_pm.get_client.return_value = MagicMock()
- mock_pm.get_model.return_value = "X1C"
- mock_relay.on_ams_change = AsyncMock()
- mock_ws.send_printer_status = AsyncMock()
- mock_ws.broadcast = AsyncMock()
- await on_ams_change(printer.id, ams_data)
- if sent is not None:
- sent.extend(c.args[0]["type"] for c in mock_ws.broadcast.call_args_list)
- db_session.expire_all()
- return (await db_session.get(Spool, spool_id)).weight_used
- @pytest.mark.asyncio
- @pytest.mark.integration
- @pytest.mark.parametrize("state", ["RUNNING", "PAUSE"])
- async def test_no_sync_while_printing_without_a_tracker_session(test_engine, db_session, printer_factory, state):
- assert await _weight_after_ams_push(test_engine, db_session, printer_factory, state) == 100
- @pytest.mark.asyncio
- @pytest.mark.integration
- async def test_syncs_when_idle(test_engine, db_session, printer_factory):
- assert await _weight_after_ams_push(test_engine, db_session, printer_factory, "IDLE") == 500
- @pytest.mark.asyncio
- @pytest.mark.integration
- async def test_a_synced_weight_is_announced(test_engine, db_session, printer_factory):
- """WU-9: other open tabs refresh weights only on inventory_changed."""
- sent: list[str] = []
- assert await _weight_after_ams_push(test_engine, db_session, printer_factory, "IDLE", sent) == 500
- assert "inventory_changed" in sent
|