| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326 |
- """Integration tests for inventory spool assignment — tray_info_idx resolution.
- Tests that the spool's own slicer_filament (including PFUS* cloud-synced
- custom presets) takes priority, with slot reuse and generic fallback as
- lower-priority fallbacks.
- """
- from unittest.mock import MagicMock, patch
- import pytest
- from httpx import AsyncClient
- from sqlalchemy.ext.asyncio import AsyncSession
- from backend.app.models.spool import Spool
- @pytest.fixture
- async def spool_factory(db_session: AsyncSession):
- """Factory to create test spools."""
- _counter = [0]
- async def _create_spool(**kwargs):
- _counter[0] += 1
- defaults = {
- "material": "PLA",
- "subtype": "Basic",
- "brand": "Devil Design",
- "color_name": "Red",
- "rgba": "FF0000FF",
- "label_weight": 1000,
- "weight_used": 0,
- "slicer_filament": "PFUS9ac902733670a9",
- }
- defaults.update(kwargs)
- spool = Spool(**defaults)
- db_session.add(spool)
- await db_session.commit()
- await db_session.refresh(spool)
- return spool
- return _create_spool
- def _make_mock_status(ams_data=None, vt_tray=None, nozzles=None, ams_extruder_map=None):
- """Build a mock printer status with optional AMS/nozzle data."""
- status = MagicMock()
- raw = {}
- if ams_data is not None:
- raw["ams"] = {"ams": ams_data}
- if vt_tray is not None:
- raw["vt_tray"] = vt_tray
- status.raw_data = raw
- status.nozzles = nozzles or [MagicMock(nozzle_diameter="0.4")]
- status.ams_extruder_map = ams_extruder_map
- return status
- class TestAssignSpoolTrayInfoIdx:
- """Tests for tray_info_idx resolution during spool assignment."""
- @pytest.mark.asyncio
- @pytest.mark.integration
- async def test_pfus_slicer_filament_used_directly(self, async_client: AsyncClient, printer_factory, spool_factory):
- """PFUS* cloud-synced custom preset IDs are sent to the printer."""
- printer = await printer_factory(name="H2D")
- spool = await spool_factory(slicer_filament="PFUS9ac902733670a9", material="PLA")
- mock_client = MagicMock()
- mock_client.ams_set_filament_setting.return_value = True
- mock_client.extrusion_cali_sel.return_value = True
- status = _make_mock_status(ams_data=[{"id": 2, "tray": [{"id": 3, "tray_info_idx": "", "tray_type": ""}]}])
- with patch("backend.app.services.printer_manager.printer_manager") as mock_pm:
- mock_pm.get_client.return_value = mock_client
- mock_pm.get_status.return_value = status
- response = await async_client.post(
- "/api/v1/inventory/assignments",
- json={"spool_id": spool.id, "printer_id": printer.id, "ams_id": 2, "tray_id": 3},
- )
- assert response.status_code == 200
- call_kwargs = mock_client.ams_set_filament_setting.call_args
- assert call_kwargs.kwargs["tray_info_idx"] == "PFUS9ac902733670a9"
- @pytest.mark.asyncio
- @pytest.mark.integration
- async def test_spool_preset_takes_priority_over_slot(
- self, async_client: AsyncClient, printer_factory, spool_factory
- ):
- """Spool's own slicer_filament takes priority over slot's existing preset."""
- printer = await printer_factory(name="H2D")
- spool = await spool_factory(slicer_filament="PFUS9ac902733670a9", material="PLA")
- mock_client = MagicMock()
- mock_client.ams_set_filament_setting.return_value = True
- mock_client.extrusion_cali_sel.return_value = True
- # Slot already configured by slicer with cloud-synced preset
- status = _make_mock_status(
- ams_data=[{"id": 2, "tray": [{"id": 3, "tray_info_idx": "P4d64437", "tray_type": "PLA"}]}]
- )
- with patch("backend.app.services.printer_manager.printer_manager") as mock_pm:
- mock_pm.get_client.return_value = mock_client
- mock_pm.get_status.return_value = status
- response = await async_client.post(
- "/api/v1/inventory/assignments",
- json={"spool_id": spool.id, "printer_id": printer.id, "ams_id": 2, "tray_id": 3},
- )
- assert response.status_code == 200
- call_kwargs = mock_client.ams_set_filament_setting.call_args
- # Spool's own preset wins over slot's existing one
- assert call_kwargs.kwargs["tray_info_idx"] == "PFUS9ac902733670a9"
- @pytest.mark.asyncio
- @pytest.mark.integration
- async def test_spool_preset_used_even_if_different_material_on_slot(
- self, async_client: AsyncClient, printer_factory, spool_factory
- ):
- """Spool's own slicer_filament is used regardless of what's on the slot."""
- printer = await printer_factory(name="H2D")
- spool = await spool_factory(slicer_filament="PFUS9ac902733670a9", material="PETG")
- mock_client = MagicMock()
- mock_client.ams_set_filament_setting.return_value = True
- mock_client.extrusion_cali_sel.return_value = True
- # Slot currently has PLA but spool is PETG
- status = _make_mock_status(
- ams_data=[{"id": 2, "tray": [{"id": 3, "tray_info_idx": "P4d64437", "tray_type": "PLA"}]}]
- )
- with patch("backend.app.services.printer_manager.printer_manager") as mock_pm:
- mock_pm.get_client.return_value = mock_client
- mock_pm.get_status.return_value = status
- response = await async_client.post(
- "/api/v1/inventory/assignments",
- json={"spool_id": spool.id, "printer_id": printer.id, "ams_id": 2, "tray_id": 3},
- )
- assert response.status_code == 200
- call_kwargs = mock_client.ams_set_filament_setting.call_args
- assert call_kwargs.kwargs["tray_info_idx"] == "PFUS9ac902733670a9"
- @pytest.mark.asyncio
- @pytest.mark.integration
- async def test_gf_slicer_filament_kept(self, async_client: AsyncClient, printer_factory, spool_factory):
- """Standard GF* IDs from spool.slicer_filament are used directly."""
- printer = await printer_factory(name="X1C")
- spool = await spool_factory(slicer_filament="GFL05", material="PLA")
- mock_client = MagicMock()
- mock_client.ams_set_filament_setting.return_value = True
- mock_client.extrusion_cali_sel.return_value = True
- status = _make_mock_status(ams_data=[])
- with patch("backend.app.services.printer_manager.printer_manager") as mock_pm:
- mock_pm.get_client.return_value = mock_client
- mock_pm.get_status.return_value = status
- response = await async_client.post(
- "/api/v1/inventory/assignments",
- json={"spool_id": spool.id, "printer_id": printer.id, "ams_id": 0, "tray_id": 0},
- )
- assert response.status_code == 200
- call_kwargs = mock_client.ams_set_filament_setting.call_args
- assert call_kwargs.kwargs["tray_info_idx"] == "GFL05"
- @pytest.mark.asyncio
- @pytest.mark.integration
- async def test_empty_slicer_filament_uses_generic(self, async_client: AsyncClient, printer_factory, spool_factory):
- """Spool with no slicer_filament gets a generic ID from material type."""
- printer = await printer_factory(name="X1C")
- spool = await spool_factory(slicer_filament=None, material="ABS")
- mock_client = MagicMock()
- mock_client.ams_set_filament_setting.return_value = True
- mock_client.extrusion_cali_sel.return_value = True
- status = _make_mock_status(ams_data=[])
- with patch("backend.app.services.printer_manager.printer_manager") as mock_pm:
- mock_pm.get_client.return_value = mock_client
- mock_pm.get_status.return_value = status
- response = await async_client.post(
- "/api/v1/inventory/assignments",
- json={"spool_id": spool.id, "printer_id": printer.id, "ams_id": 0, "tray_id": 0},
- )
- assert response.status_code == 200
- call_kwargs = mock_client.ams_set_filament_setting.call_args
- assert call_kwargs.kwargs["tray_info_idx"] == "GFB99"
- @pytest.mark.asyncio
- @pytest.mark.integration
- async def test_spool_pfus_used_over_slot_pfus(self, async_client: AsyncClient, printer_factory, spool_factory):
- """Spool's own PFUS preset is used even when slot has a different PFUS."""
- printer = await printer_factory(name="H2D")
- spool = await spool_factory(slicer_filament="PFUS1111111111", material="PLA")
- mock_client = MagicMock()
- mock_client.ams_set_filament_setting.return_value = True
- mock_client.extrusion_cali_sel.return_value = True
- # Slot has a PFUS* ID from some previous config
- status = _make_mock_status(
- ams_data=[{"id": 0, "tray": [{"id": 0, "tray_info_idx": "PFUS2222222222", "tray_type": "PLA"}]}]
- )
- with patch("backend.app.services.printer_manager.printer_manager") as mock_pm:
- mock_pm.get_client.return_value = mock_client
- mock_pm.get_status.return_value = status
- response = await async_client.post(
- "/api/v1/inventory/assignments",
- json={"spool_id": spool.id, "printer_id": printer.id, "ams_id": 0, "tray_id": 0},
- )
- assert response.status_code == 200
- call_kwargs = mock_client.ams_set_filament_setting.call_args
- # Spool's own preset wins
- assert call_kwargs.kwargs["tray_info_idx"] == "PFUS1111111111"
- @pytest.mark.asyncio
- @pytest.mark.integration
- async def test_generic_on_slot_not_reused_over_spool_preset(
- self, async_client: AsyncClient, printer_factory, spool_factory
- ):
- """Generic ID on slot (e.g. GFB99) must not override spool's own preset."""
- printer = await printer_factory(name="P2S")
- spool = await spool_factory(slicer_filament="PFUScda4c46fc9031", material="ABS")
- mock_client = MagicMock()
- mock_client.ams_set_filament_setting.return_value = True
- mock_client.extrusion_cali_sel.return_value = True
- # Slot stuck on generic ABS from a previous assignment
- status = _make_mock_status(
- ams_data=[{"id": 0, "tray": [{"id": 1, "tray_info_idx": "GFB99", "tray_type": "ABS"}]}]
- )
- with patch("backend.app.services.printer_manager.printer_manager") as mock_pm:
- mock_pm.get_client.return_value = mock_client
- mock_pm.get_status.return_value = status
- response = await async_client.post(
- "/api/v1/inventory/assignments",
- json={"spool_id": spool.id, "printer_id": printer.id, "ams_id": 0, "tray_id": 1},
- )
- assert response.status_code == 200
- call_kwargs = mock_client.ams_set_filament_setting.call_args
- # Spool's preset wins — generic on slot must not be sticky
- assert call_kwargs.kwargs["tray_info_idx"] == "PFUScda4c46fc9031"
- @pytest.mark.asyncio
- @pytest.mark.integration
- async def test_no_preset_with_generic_on_slot_still_uses_generic(
- self, async_client: AsyncClient, printer_factory, spool_factory
- ):
- """Spool without preset + generic on slot → generic fallback (not slot reuse)."""
- printer = await printer_factory(name="P2S")
- spool = await spool_factory(slicer_filament=None, material="ABS")
- mock_client = MagicMock()
- mock_client.ams_set_filament_setting.return_value = True
- mock_client.extrusion_cali_sel.return_value = True
- # Slot has generic ABS
- status = _make_mock_status(
- ams_data=[{"id": 0, "tray": [{"id": 1, "tray_info_idx": "GFB99", "tray_type": "ABS"}]}]
- )
- with patch("backend.app.services.printer_manager.printer_manager") as mock_pm:
- mock_pm.get_client.return_value = mock_client
- mock_pm.get_status.return_value = status
- response = await async_client.post(
- "/api/v1/inventory/assignments",
- json={"spool_id": spool.id, "printer_id": printer.id, "ams_id": 0, "tray_id": 1},
- )
- assert response.status_code == 200
- call_kwargs = mock_client.ams_set_filament_setting.call_args
- # Still gets generic, but via fallback — not via sticky reuse
- assert call_kwargs.kwargs["tray_info_idx"] == "GFB99"
- @pytest.mark.asyncio
- @pytest.mark.integration
- async def test_no_preset_reuses_specific_slot_preset(
- self, async_client: AsyncClient, printer_factory, spool_factory
- ):
- """Spool without preset + specific preset on slot → reuse slot's preset."""
- printer = await printer_factory(name="X1C")
- spool = await spool_factory(slicer_filament=None, material="PLA")
- mock_client = MagicMock()
- mock_client.ams_set_filament_setting.return_value = True
- mock_client.extrusion_cali_sel.return_value = True
- # Slot has a specific Bambu PLA preset (not generic)
- status = _make_mock_status(
- ams_data=[{"id": 0, "tray": [{"id": 0, "tray_info_idx": "GFA05", "tray_type": "PLA"}]}]
- )
- with patch("backend.app.services.printer_manager.printer_manager") as mock_pm:
- mock_pm.get_client.return_value = mock_client
- mock_pm.get_status.return_value = status
- response = await async_client.post(
- "/api/v1/inventory/assignments",
- json={"spool_id": spool.id, "printer_id": printer.id, "ams_id": 0, "tray_id": 0},
- )
- assert response.status_code == 200
- call_kwargs = mock_client.ams_set_filament_setting.call_args
- # Slot's specific preset is reused when spool has no own preset
- assert call_kwargs.kwargs["tray_info_idx"] == "GFA05"
|