Add virtual printer model selection

Features:
  - Configurable printer model for virtual printer emulation
  - Supports X1 series (X1C, X1, X1E), P series (P1S, P1P, P2S),
    A1 series (A1, A1 Mini), and H2 series (H2D, H2C, H2S)
  - Dropdown in Settings > Virtual Printer to select model
  - Model affects SSDP discovery and slicer compatibility
  - Model change restarts virtual printer services automatically

  Backend:
  - Added VIRTUAL_PRINTER_MODELS mapping in manager.py
  - Added virtual_printer_model setting in database
  - New GET /api/v1/settings/virtual-printer/models endpoint
  - Updated PUT /api/v1/settings/virtual-printer to accept model

  Frontend:
  - Added model dropdown to VirtualPrinterSettings component
  - Status display shows selected model name
  - Model change disabled while virtual printer is running

  Tests:
  - Added 3 unit tests for model configuration
  - Updated frontend test mocks for getModels API
This commit is contained in:
maziggy
2026-01-04 09:39:06 +01:00
parent 8639f39028
commit 81c8dd7d0c
13 changed files with 304 additions and 78 deletions
@@ -59,6 +59,54 @@ class TestVirtualPrinterManager:
with pytest.raises(ValueError, match="Access code is required"):
await manager.configure(enabled=True)
@pytest.mark.asyncio
async def test_configure_sets_model(self, manager):
"""Verify configure stores model correctly."""
manager._start = AsyncMock()
await manager.configure(
enabled=True,
access_code="12345678",
mode="immediate",
model="C11", # P1S model code
)
assert manager._model == "C11"
@pytest.mark.asyncio
async def test_configure_ignores_invalid_model(self, manager):
"""Verify configure ignores invalid model codes."""
manager._start = AsyncMock()
await manager.configure(
enabled=True,
access_code="12345678",
model="INVALID",
)
# Should keep default model
assert manager._model == "BL-P001"
@pytest.mark.asyncio
async def test_configure_restarts_on_model_change(self, manager):
"""Verify model change restarts services when running."""
# Simulate running state
manager._enabled = True
manager._model = "BL-P001"
manager._tasks = [MagicMock(done=MagicMock(return_value=False))]
manager._stop = AsyncMock()
manager._start = AsyncMock()
await manager.configure(
enabled=True,
access_code="12345678",
model="C11",
)
# Should have stopped and started
manager._stop.assert_called_once()
manager._start.assert_called_once()
# ========================================================================
# Tests for status
# ========================================================================
@@ -67,6 +115,7 @@ class TestVirtualPrinterManager:
"""Verify get_status returns expected fields."""
manager._enabled = True
manager._mode = "immediate"
manager._model = "C11"
manager._pending_files = {"file1.3mf": Path("/tmp/file1.3mf")}
# Simulate running tasks
manager._tasks = [MagicMock(done=MagicMock(return_value=False))]
@@ -78,6 +127,8 @@ class TestVirtualPrinterManager:
assert status["mode"] == "immediate"
assert status["name"] == "Bambuddy"
assert status["serial"] == "00M09A391800001"
assert status["model"] == "C11"
assert status["model_name"] == "P1S"
assert status["pending_files"] == 1
def test_get_status_when_stopped(self, manager):