| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137 |
- """Tests for DELETE /virtual-printers/{vp_id} orphan cleanup.
- Before the fix, deleting a VP only stopped the running instance and
- removed the row. The on-disk ``base_dir/uploads/<vp_id>/`` directory
- lingered, and any ``PendingUpload`` rows that pointed into it remained
- in ``pending`` status — showing up as phantom entries in
- ``/pending-uploads/``. The route now (a) marks those rows as
- ``discarded`` and (b) ``shutil.rmtree``s the upload_dir after the DB
- commit succeeds.
- """
- from unittest.mock import AsyncMock, MagicMock, patch
- import pytest
- from backend.app.api.routes.virtual_printers import delete_virtual_printer
- @pytest.mark.asyncio
- async def test_delete_vp_marks_orphan_pending_uploads_discarded(tmp_path):
- """A VP with PendingUpload rows pointing at its upload_dir: after
- DELETE, those rows must be flipped to ``discarded`` and the on-disk
- directory must be gone."""
- vp_id = 77
- upload_dir = tmp_path / "uploads" / str(vp_id)
- upload_dir.mkdir(parents=True)
- (upload_dir / "stale.3mf").write_bytes(b"orphaned content")
- # Build PendingUpload-like mocks. The route mutates `.status`.
- pending_a = MagicMock()
- pending_a.file_path = str(upload_dir / "stale.3mf")
- pending_a.status = "pending"
- pending_b = MagicMock()
- pending_b.file_path = str(upload_dir / "another.3mf")
- pending_b.status = "pending"
- # Unrelated PendingUpload that does NOT belong to this VP — must
- # be left alone.
- other_pending = MagicMock()
- other_pending.file_path = str(tmp_path / "uploads" / "99" / "not-mine.3mf")
- other_pending.status = "pending"
- # Mock VP row.
- vp_row = MagicMock()
- vp_row.id = vp_id
- vp_row.name = "DeleteMe"
- # Mock DB session with the route's two .execute() calls + flush + commit.
- select_calls = {"i": 0}
- async def fake_execute(query): # noqa: ARG001
- """Return the VP row on the first call (vp lookup) and the
- in-range PendingUpload rows on the second call (orphan query).
- Third call is the DELETE which doesn't need a result."""
- select_calls["i"] += 1
- result = MagicMock()
- if select_calls["i"] == 1:
- result.scalar_one_or_none = MagicMock(return_value=vp_row)
- elif select_calls["i"] == 2:
- scalars = MagicMock()
- scalars.all = MagicMock(return_value=[pending_a, pending_b])
- result.scalars = MagicMock(return_value=scalars)
- return result
- db = AsyncMock()
- db.execute = fake_execute
- db.flush = AsyncMock()
- db.commit = AsyncMock()
- # Mock the manager: remove_instance, _base_dir, sync_from_db.
- fake_manager = MagicMock()
- fake_manager.remove_instance = AsyncMock()
- fake_manager.sync_from_db = AsyncMock()
- fake_manager._base_dir = tmp_path
- with patch(
- "backend.app.services.virtual_printer.virtual_printer_manager",
- fake_manager,
- ):
- await delete_virtual_printer(vp_id=vp_id, db=db, _=None)
- # Both in-range PendingUpload rows must be flipped to "discarded".
- assert pending_a.status == "discarded"
- assert pending_b.status == "discarded"
- # The unrelated row was never returned from the query — left alone.
- assert other_pending.status == "pending"
- # The on-disk upload_dir is gone.
- assert not upload_dir.exists()
- # The running instance was stopped before the row was removed.
- fake_manager.remove_instance.assert_awaited_once_with(vp_id)
- @pytest.mark.asyncio
- async def test_delete_vp_with_no_orphan_uploads_still_succeeds(tmp_path):
- """A VP with no PendingUpload rows and no upload_dir on disk: the
- cleanup path must be a clean no-op, not raise."""
- vp_id = 88
- vp_row = MagicMock()
- vp_row.id = vp_id
- vp_row.name = "EmptyDelete"
- select_calls = {"i": 0}
- async def fake_execute(query): # noqa: ARG001
- select_calls["i"] += 1
- result = MagicMock()
- if select_calls["i"] == 1:
- result.scalar_one_or_none = MagicMock(return_value=vp_row)
- elif select_calls["i"] == 2:
- # No PendingUpload rows match.
- scalars = MagicMock()
- scalars.all = MagicMock(return_value=[])
- result.scalars = MagicMock(return_value=scalars)
- return result
- db = AsyncMock()
- db.execute = fake_execute
- db.flush = AsyncMock()
- db.commit = AsyncMock()
- fake_manager = MagicMock()
- fake_manager.remove_instance = AsyncMock()
- fake_manager.sync_from_db = AsyncMock()
- fake_manager._base_dir = tmp_path # no uploads/<vp_id> exists
- with patch(
- "backend.app.services.virtual_printer.virtual_printer_manager",
- fake_manager,
- ):
- await delete_virtual_printer(vp_id=vp_id, db=db, _=None)
- fake_manager.remove_instance.assert_awaited_once_with(vp_id)
- # No directory to remove — and we didn't crash trying to.
- assert not (tmp_path / "uploads" / str(vp_id)).exists()
|