Просмотр исходного кода

fix(vp): honor workflow default print options in queue-mode receive (#1235)

  Prints sent from a slicer to a VP in print_queue mode arrived in the
  queue with bed_levelling / flow_cali / vibration_cali / layer_inspect /
  timelapse set to the SQLAlchemy column defaults, ignoring the user's
  workflow page settings entirely. The manual POST /print-queue endpoint
  reads these from the request body (frontend pulls them from settings
  before submitting), but manager._add_to_print_queue constructed the
  PrintQueueItem without touching any of those fields.

  Read default_bed_levelling and the other four settings via get_setting
  and pass them explicitly. _bool_setting helper handles the None ->
  AppSettings default fallback.
maziggy 3 месяцев назад
Родитель
Сommit
ae43ced0ef

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
CHANGELOG.md


+ 19 - 0
backend/app/services/virtual_printer/manager.py

@@ -345,6 +345,20 @@ class VirtualPrinterInstance:
             async with self._session_factory() as db:
                 name_source = await get_setting(db, "virtual_printer_archive_name_source")
                 prefer_filename = name_source == "filename"
+
+                # Read workflow defaults from settings. Without this the
+                # PrintQueueItem below would fall back to the column-level
+                # defaults and ignore the user's workflow preferences (#1235).
+                # Fallbacks match AppSettings defaults in schemas/settings.py.
+                def _bool_setting(value: str | None, default: bool) -> bool:
+                    return value.lower() == "true" if value is not None else default
+
+                bed_levelling = _bool_setting(await get_setting(db, "default_bed_levelling"), True)
+                flow_cali = _bool_setting(await get_setting(db, "default_flow_cali"), False)
+                vibration_cali = _bool_setting(await get_setting(db, "default_vibration_cali"), True)
+                layer_inspect = _bool_setting(await get_setting(db, "default_layer_inspect"), False)
+                timelapse = _bool_setting(await get_setting(db, "default_timelapse"), False)
+
                 service = ArchiveService(db)
                 archive = await service.archive_print(
                     printer_id=None,
@@ -405,6 +419,11 @@ class VirtualPrinterInstance:
                         manual_start=not self.auto_dispatch,
                         required_filament_types=required_filament_types_json,
                         filament_overrides=filament_overrides_json,
+                        bed_levelling=bed_levelling,
+                        flow_cali=flow_cali,
+                        vibration_cali=vibration_cali,
+                        layer_inspect=layer_inspect,
+                        timelapse=timelapse,
                     )
                     db.add(queue_item)
                     await db.commit()

+ 136 - 0
backend/tests/unit/services/test_virtual_printer.py

@@ -317,6 +317,142 @@ class TestVirtualPrinterInstance:
         queue_item = added_items[0]
         assert queue_item.manual_start is True
 
+    @pytest.mark.asyncio
+    async def test_add_to_print_queue_uses_workflow_defaults_from_settings(self, tmp_path):
+        """#1235: VP queue-mode constructed PrintQueueItem without specifying
+        bed_levelling / flow_cali / vibration_cali / layer_inspect / timelapse,
+        so SQLAlchemy applied the column-level defaults and ignored the user's
+        workflow preferences entirely. Every print sent from the slicer to the
+        VP came through with the OPPOSITE of what the workflow page said,
+        forcing the user to edit each queue item by hand.
+        """
+        from backend.app.services.virtual_printer.manager import VirtualPrinterInstance
+
+        added_items = []
+        mock_db = AsyncMock()
+        mock_db.add = MagicMock(side_effect=added_items.append)
+        mock_db.commit = AsyncMock()
+        mock_session_factory = MagicMock()
+        mock_session_ctx = AsyncMock()
+        mock_session_ctx.__aenter__ = AsyncMock(return_value=mock_db)
+        mock_session_ctx.__aexit__ = AsyncMock(return_value=False)
+        mock_session_factory.return_value = mock_session_ctx
+
+        inst = VirtualPrinterInstance(
+            vp_id=22,
+            name="DefaultsTest",
+            mode="print_queue",
+            model="C12",
+            access_code="12345678",
+            serial_suffix="391800022",
+            auto_dispatch=True,
+            base_dir=tmp_path,
+            session_factory=mock_session_factory,
+        )
+
+        file_path = tmp_path / "test.3mf"
+        file_path.write_bytes(b"fake3mf")
+
+        # The reporter set every workflow default to the OPPOSITE of the model's
+        # column default. Pre-fix the column defaults won; with the fix the
+        # settings values must flow through to the queue item exactly as stored.
+        settings_map = {
+            "virtual_printer_archive_name_source": None,
+            "default_bed_levelling": "false",  # model default: True
+            "default_flow_cali": "true",  # model default: False
+            "default_vibration_cali": "false",  # model default: True
+            "default_layer_inspect": "true",  # model default: False
+            "default_timelapse": "true",  # model default: False
+        }
+
+        async def fake_get_setting(_db, key):
+            return settings_map.get(key)
+
+        mock_archive = MagicMock()
+        mock_archive.id = 1
+        mock_archive.print_name = "test"
+
+        with (
+            patch(
+                "backend.app.api.routes.settings.get_setting",
+                new=fake_get_setting,
+            ),
+            patch(
+                "backend.app.services.archive.ArchiveService.archive_print",
+                new_callable=AsyncMock,
+                return_value=mock_archive,
+            ),
+        ):
+            await inst._add_to_print_queue(file_path, "192.168.1.100")
+
+        assert len(added_items) == 1
+        queue_item = added_items[0]
+        assert queue_item.bed_levelling is False, "default_bed_levelling=false must flow through"
+        assert queue_item.flow_cali is True, "default_flow_cali=true must flow through"
+        assert queue_item.vibration_cali is False, "default_vibration_cali=false must flow through"
+        assert queue_item.layer_inspect is True, "default_layer_inspect=true must flow through"
+        assert queue_item.timelapse is True, "default_timelapse=true must flow through"
+
+    @pytest.mark.asyncio
+    async def test_add_to_print_queue_falls_back_to_schema_defaults_when_unset(self, tmp_path):
+        """#1235 fallback: when no workflow setting is in the DB, the queue
+        item should use the AppSettings (Pydantic) defaults — same values
+        the user sees in the workflow page on a fresh install.
+        """
+        from backend.app.services.virtual_printer.manager import VirtualPrinterInstance
+
+        added_items = []
+        mock_db = AsyncMock()
+        mock_db.add = MagicMock(side_effect=added_items.append)
+        mock_db.commit = AsyncMock()
+        mock_session_factory = MagicMock()
+        mock_session_ctx = AsyncMock()
+        mock_session_ctx.__aenter__ = AsyncMock(return_value=mock_db)
+        mock_session_ctx.__aexit__ = AsyncMock(return_value=False)
+        mock_session_factory.return_value = mock_session_ctx
+
+        inst = VirtualPrinterInstance(
+            vp_id=23,
+            name="FreshInstallDefaults",
+            mode="print_queue",
+            model="C12",
+            access_code="12345678",
+            serial_suffix="391800023",
+            auto_dispatch=True,
+            base_dir=tmp_path,
+            session_factory=mock_session_factory,
+        )
+
+        file_path = tmp_path / "test.3mf"
+        file_path.write_bytes(b"fake3mf")
+
+        mock_archive = MagicMock()
+        mock_archive.id = 1
+        mock_archive.print_name = "test"
+
+        with (
+            patch(
+                "backend.app.api.routes.settings.get_setting",
+                new_callable=AsyncMock,
+                return_value=None,  # No settings → fall back to schema defaults
+            ),
+            patch(
+                "backend.app.services.archive.ArchiveService.archive_print",
+                new_callable=AsyncMock,
+                return_value=mock_archive,
+            ),
+        ):
+            await inst._add_to_print_queue(file_path, "192.168.1.100")
+
+        assert len(added_items) == 1
+        queue_item = added_items[0]
+        # These must match the AppSettings (Pydantic) defaults in schemas/settings.py
+        assert queue_item.bed_levelling is True
+        assert queue_item.flow_cali is False
+        assert queue_item.vibration_cali is True
+        assert queue_item.layer_inspect is False
+        assert queue_item.timelapse is False
+
     @pytest.mark.asyncio
     async def test_add_to_print_queue_populates_required_filament_types(self, tmp_path):
         """#1188: VP queue-mode used to create PrintQueueItems with no

Некоторые файлы не были показаны из-за большого количества измененных файлов