|
|
@@ -599,3 +599,83 @@ class TestVirtualPrinterAccessCodeInheritance:
|
|
|
|
|
|
vp = (await db_session.execute(select(VirtualPrinter).where(VirtualPrinter.id == vp_id))).scalar_one()
|
|
|
assert vp.access_code == "REALCODE"
|
|
|
+
|
|
|
+
|
|
|
+class TestVirtualPrinterSerialSurface:
|
|
|
+ """Proxy-mode VPs must surface the target printer's serial in API responses.
|
|
|
+
|
|
|
+ The bridge advertises the target's serial over SSDP and forwards the
|
|
|
+ target's identity to the slicer; the VP-settings card should show the
|
|
|
+ same serial so the user sees one consistent identity per VP. Archive /
|
|
|
+ queue / review VPs keep the self-generated suffix-based serial since
|
|
|
+ those modes never speak the target's identity.
|
|
|
+ """
|
|
|
+
|
|
|
+ @pytest.mark.asyncio
|
|
|
+ @pytest.mark.integration
|
|
|
+ async def test_proxy_vp_response_uses_target_printer_serial(self, async_client: AsyncClient, printer_factory):
|
|
|
+ target = await printer_factory(name="Real X1C", access_code="REALCODE", serial_number="00M09A123456789")
|
|
|
+
|
|
|
+ create_resp = await async_client.post(
|
|
|
+ "/api/v1/virtual-printers",
|
|
|
+ json={
|
|
|
+ "name": "ProxyVP",
|
|
|
+ "mode": "proxy",
|
|
|
+ "target_printer_id": target.id,
|
|
|
+ },
|
|
|
+ )
|
|
|
+ assert create_resp.status_code == 200
|
|
|
+ vp_id = create_resp.json()["id"]
|
|
|
+ assert create_resp.json()["serial"] == "00M09A123456789"
|
|
|
+
|
|
|
+ get_resp = await async_client.get(f"/api/v1/virtual-printers/{vp_id}")
|
|
|
+ assert get_resp.status_code == 200
|
|
|
+ assert get_resp.json()["serial"] == "00M09A123456789"
|
|
|
+
|
|
|
+ list_resp = await async_client.get("/api/v1/virtual-printers")
|
|
|
+ assert list_resp.status_code == 200
|
|
|
+ listed = next(p for p in list_resp.json()["printers"] if p["id"] == vp_id)
|
|
|
+ assert listed["serial"] == "00M09A123456789"
|
|
|
+
|
|
|
+ @pytest.mark.asyncio
|
|
|
+ @pytest.mark.integration
|
|
|
+ async def test_non_proxy_vp_keeps_self_generated_serial(self, async_client: AsyncClient, printer_factory):
|
|
|
+ # Even with a target printer set (#1429 access-code inheritance flow),
|
|
|
+ # archive / queue / review VPs are NOT bridging the target's identity
|
|
|
+ # to the slicer — they synthesise their own. Self-generated serial.
|
|
|
+ target = await printer_factory(name="Real X1C", access_code="REALCODE", serial_number="00M09A123456789")
|
|
|
+
|
|
|
+ create_resp = await async_client.post(
|
|
|
+ "/api/v1/virtual-printers",
|
|
|
+ json={
|
|
|
+ "name": "QueueVP",
|
|
|
+ "mode": "queue",
|
|
|
+ "target_printer_id": target.id,
|
|
|
+ },
|
|
|
+ )
|
|
|
+ assert create_resp.status_code == 200
|
|
|
+ assert create_resp.json()["serial"] != "00M09A123456789"
|
|
|
+ # The synthesised serial follows _get_serial_for_model's `<prefix><suffix>`
|
|
|
+ # shape — model-specific prefix + 8-char hex suffix from `vp.serial_suffix`.
|
|
|
+ assert len(create_resp.json()["serial"]) >= 8
|
|
|
+
|
|
|
+ @pytest.mark.asyncio
|
|
|
+ @pytest.mark.integration
|
|
|
+ async def test_proxy_vp_falls_back_to_self_generated_when_target_missing(
|
|
|
+ self, async_client: AsyncClient, db_session
|
|
|
+ ):
|
|
|
+ # Defensive fallback: a proxy VP whose target_printer_id points at a
|
|
|
+ # row that no longer exists (printer deleted mid-config, manual SQL
|
|
|
+ # tweak, race) must not 500 — it returns the self-generated serial
|
|
|
+ # so the card still renders and the user can fix the target.
|
|
|
+ from backend.app.models.virtual_printer import VirtualPrinter
|
|
|
+
|
|
|
+ vp = VirtualPrinter(name="OrphanProxy", mode="proxy", target_printer_id=99999, enabled=False)
|
|
|
+ db_session.add(vp)
|
|
|
+ await db_session.commit()
|
|
|
+
|
|
|
+ get_resp = await async_client.get(f"/api/v1/virtual-printers/{vp.id}")
|
|
|
+ assert get_resp.status_code == 200
|
|
|
+ body = get_resp.json()
|
|
|
+ assert body["serial"] # non-empty
|
|
|
+ assert len(body["serial"]) >= 8
|