test_vp_library_store.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. """A virtual printer's upload stored in the File Manager (library_store)."""
  2. from pathlib import Path
  3. import pytest
  4. from sqlalchemy import func, select
  5. from backend.app.models.library import LibraryFile, LibraryFolder
  6. from backend.app.models.virtual_printer import VirtualPrinter
  7. from backend.app.services.virtual_printer.library_store import store_upload, upload_folder, upload_settings
  8. pytestmark = [pytest.mark.asyncio, pytest.mark.integration]
  9. @pytest.fixture(autouse=True)
  10. def _library_in_tmp(tmp_path, monkeypatch):
  11. """The File Manager writes below archive_dir: keep it out of the real one."""
  12. from backend.app.core.config import settings
  13. monkeypatch.setattr(settings, "archive_dir", tmp_path / "archive")
  14. def _upload(tmp_path: Path, name: str = "Plate_1.gcode.3mf", content: bytes = b"plate one") -> Path:
  15. path = tmp_path / "uploads" / name
  16. path.parent.mkdir(exist_ok=True)
  17. path.write_bytes(content)
  18. return path
  19. async def _files(db) -> int:
  20. return (await db.execute(select(func.count(LibraryFile.id)))).scalar()
  21. async def test_stored_with_its_name_and_copied_from_disk(db_session, tmp_path):
  22. source = _upload(tmp_path)
  23. library_file, reused = await store_upload(db_session, source_path=source, filename=source.name, folder_id=None)
  24. assert reused is False
  25. assert library_file.filename == "Plate_1.gcode.3mf"
  26. assert library_file.source_type == "virtual_printer"
  27. assert library_file.file_size == len(b"plate one")
  28. from backend.app.api.routes.library import get_library_files_dir
  29. assert (get_library_files_dir() / Path(library_file.file_path).name).read_bytes() == b"plate one"
  30. assert source.exists(), "the caller removes its temp file, not the store"
  31. async def test_the_same_file_sent_again_is_the_file_already_there(db_session, tmp_path):
  32. first, _ = await store_upload(
  33. db_session, source_path=_upload(tmp_path), filename="Plate_1.gcode.3mf", folder_id=None
  34. )
  35. again, reused = await store_upload(
  36. db_session, source_path=_upload(tmp_path), filename="Plate_1.gcode.3mf", folder_id=None
  37. )
  38. assert reused is True
  39. assert again.id == first.id
  40. assert await _files(db_session) == 1
  41. @pytest.mark.parametrize(
  42. ("name", "content"),
  43. [("Plate_1.gcode.3mf", b"changed plate"), ("Other.gcode.3mf", b"plate one")],
  44. ids=["other-content", "other-name"],
  45. )
  46. async def test_other_content_or_another_name_is_a_new_file(db_session, tmp_path, name, content):
  47. await store_upload(db_session, source_path=_upload(tmp_path), filename="Plate_1.gcode.3mf", folder_id=None)
  48. _, reused = await store_upload(
  49. db_session, source_path=_upload(tmp_path, name, content), filename=name, folder_id=None
  50. )
  51. assert reused is False
  52. assert await _files(db_session) == 2
  53. async def test_a_file_put_there_by_hand_is_never_taken_over(db_session, tmp_path):
  54. first, _ = await store_upload(
  55. db_session, source_path=_upload(tmp_path), filename="Plate_1.gcode.3mf", folder_id=None
  56. )
  57. first.source_type = None # as an upload through the File Manager would be
  58. await db_session.commit()
  59. again, reused = await store_upload(
  60. db_session, source_path=_upload(tmp_path), filename="Plate_1.gcode.3mf", folder_id=None
  61. )
  62. assert reused is False
  63. assert again.id != first.id
  64. async def test_the_configured_folder_is_used(db_session):
  65. folder = LibraryFolder(name="Class 7b", shared=True)
  66. db_session.add(folder)
  67. await db_session.flush()
  68. assert (await upload_folder(db_session, folder.id)).id == folder.id
  69. @pytest.mark.parametrize("case", ["none", "deleted", "external"])
  70. async def test_otherwise_the_shared_virtual_printer_folder(db_session, tmp_path, case):
  71. folder_id = None
  72. if case == "deleted":
  73. folder_id = 4242
  74. elif case == "external":
  75. mount = LibraryFolder(name="NAS", is_external=True, external_path=str(tmp_path), shared=True)
  76. db_session.add(mount)
  77. await db_session.flush()
  78. folder_id = mount.id
  79. folder = await upload_folder(db_session, folder_id)
  80. assert folder.name == "Virtual Printer"
  81. assert folder.parent_id is None
  82. assert folder.is_external is False
  83. assert folder.shared is True
  84. # One shared folder, not one per upload.
  85. assert (await upload_folder(db_session, None)).id == folder.id
  86. async def test_upload_settings_are_read_from_the_virtual_printer(db_session):
  87. folder = LibraryFolder(name="Queue", shared=True)
  88. db_session.add(folder)
  89. await db_session.flush()
  90. vp = VirtualPrinter(name="Q", mode="queue", library_folder_id=folder.id, queue_remove_after_print=True)
  91. db_session.add(vp)
  92. await db_session.flush()
  93. assert await upload_settings(db_session, vp.id) == (folder.id, True)
  94. assert await upload_settings(db_session, 9999) == (None, False)
  95. async def test_a_users_private_folder_of_that_name_is_not_the_shared_one(db_session):
  96. # With auth on, a user may make a private top-level "Virtual Printer"
  97. # folder; uploads of every VP without a folder must not vanish into it.
  98. from backend.app.models.user import User
  99. alice = User(username="alice", password_hash="x", role="user")
  100. db_session.add(alice)
  101. await db_session.flush()
  102. private = LibraryFolder(name="Virtual Printer", created_by_id=alice.id, shared=False)
  103. db_session.add(private)
  104. await db_session.flush()
  105. folder = await upload_folder(db_session, None)
  106. assert folder.id != private.id
  107. assert folder.shared is True
  108. assert folder.created_by_id is None
  109. async def test_a_file_a_queue_job_removes_once_it_prints_is_not_shared(db_session, tmp_path):
  110. from backend.app.models.print_queue import PrintQueueItem
  111. # The queue VP with "Remove from File Manager once it prints" gets a file
  112. # of its own, even with the same file already in the folder ...
  113. kept, _ = await store_upload(
  114. db_session, source_path=_upload(tmp_path), filename="Plate_1.gcode.3mf", folder_id=None
  115. )
  116. own, reused = await store_upload(
  117. db_session, source_path=_upload(tmp_path), filename="Plate_1.gcode.3mf", folder_id=None, reuse=False
  118. )
  119. assert reused is False
  120. assert own.id != kept.id
  121. db_session.add(PrintQueueItem(library_file_id=own.id, status="pending", cleanup_library_after_dispatch=True))
  122. await db_session.commit()
  123. # ... and a later upload of it never takes that one, which its print removes.
  124. again, reused = await store_upload(
  125. db_session, source_path=_upload(tmp_path), filename="Plate_1.gcode.3mf", folder_id=None
  126. )
  127. assert reused is True
  128. assert again.id == kept.id
  129. await db_session.delete(kept)
  130. await db_session.commit()
  131. fresh, reused = await store_upload(
  132. db_session, source_path=_upload(tmp_path), filename="Plate_1.gcode.3mf", folder_id=None
  133. )
  134. assert reused is False
  135. assert fresh.id not in (kept.id, own.id)