test_confirm_outcome_queue_defaults_1898.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. """Every queue-creation path has to be able to ask for the outcome (#1898).
  2. ``confirm_outcome`` rides from a queue item onto the archive at dispatch, and
  3. the only place that ever set it was the print dialog. Jobs created anywhere
  4. else -- a plate sent from Bambu Studio to a virtual printer, the Library's bulk
  5. "Add to queue", the webhook, a pipeline run -- carried the column default and
  6. were never asked about, however the install's settings were configured.
  7. That mattered most for the virtual printer: a Bambu Studio plate is one of the
  8. prints ``confirm_outcome_external_prints`` names in its own description, but it
  9. arrives with a queue item, so ``on_print_start`` resumes its archive instead of
  10. treating it as external and the setting could not reach it at all.
  11. """
  12. from pathlib import Path
  13. from uuid import uuid4
  14. import pytest
  15. from httpx import AsyncClient
  16. from sqlalchemy import select
  17. from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
  18. from backend.app.core.config import settings as app_settings
  19. from backend.app.models.print_queue import PrintQueueItem
  20. from backend.app.models.settings import Settings
  21. from backend.app.services.print_confirmation import confirm_outcome_for_new_queue_item
  22. pytestmark = pytest.mark.integration
  23. async def _set_setting(db_session, key: str, value: str) -> None:
  24. db_session.add(Settings(key=key, value=value))
  25. await db_session.commit()
  26. class TestTheDefaultForAQueueItemNobodyToggled:
  27. @pytest.mark.asyncio
  28. async def test_off_when_nothing_is_configured(self, db_session):
  29. assert await confirm_outcome_for_new_queue_item(db_session) is False
  30. assert await confirm_outcome_for_new_queue_item(db_session, started_outside_bambuddy=True) is False
  31. @pytest.mark.asyncio
  32. async def test_the_per_job_default_covers_every_path(self, db_session):
  33. """Same setting the print dialog seeds its own toggle from."""
  34. await _set_setting(db_session, "default_confirm_outcome", "true")
  35. assert await confirm_outcome_for_new_queue_item(db_session) is True
  36. assert await confirm_outcome_for_new_queue_item(db_session, started_outside_bambuddy=True) is True
  37. @pytest.mark.asyncio
  38. async def test_the_external_setting_only_covers_external_origins(self, db_session):
  39. """A Bambu Studio plate counts; a pipeline run Bambuddy started itself
  40. does not -- that one follows the per-job default like any queued job."""
  41. await _set_setting(db_session, "confirm_outcome_external_prints", "true")
  42. assert await confirm_outcome_for_new_queue_item(db_session, started_outside_bambuddy=True) is True
  43. assert await confirm_outcome_for_new_queue_item(db_session) is False
  44. @pytest.mark.asyncio
  45. async def test_an_odd_stored_value_counts_as_off(self, db_session):
  46. await _set_setting(db_session, "default_confirm_outcome", "None")
  47. assert await confirm_outcome_for_new_queue_item(db_session) is False
  48. @pytest.fixture
  49. async def sliced_library_file(db_session):
  50. """A library file that passes add-to-queue's gates: the filename has to look
  51. sliced and the bytes have to exist under ``base_dir``."""
  52. from backend.app.models.library import LibraryFile
  53. # Unique per test: the suite runs with xdist and the teardown below would
  54. # otherwise delete the bytes a sibling worker is still relying on, which the
  55. # route answers with a 400 for a bulk add that queued nothing (#3112).
  56. name = f"confirm_outcome_probe_{uuid4().hex}.gcode.3mf"
  57. rel_path = f"archive/library/files/{name}"
  58. abs_path = Path(app_settings.base_dir) / rel_path
  59. abs_path.parent.mkdir(parents=True, exist_ok=True)
  60. abs_path.write_bytes(b"probe")
  61. lib_file = LibraryFile(
  62. filename=name,
  63. file_path=rel_path,
  64. file_size=5,
  65. file_type="3mf",
  66. )
  67. db_session.add(lib_file)
  68. await db_session.commit()
  69. await db_session.refresh(lib_file)
  70. yield lib_file
  71. abs_path.unlink(missing_ok=True)
  72. async def _read_item(test_engine, item_id: int) -> PrintQueueItem:
  73. """Fresh-session read: the route ran on its own ``Depends(get_db)`` session."""
  74. maker = async_sessionmaker(test_engine, class_=AsyncSession, expire_on_commit=False)
  75. async with maker() as fresh:
  76. return (await fresh.execute(select(PrintQueueItem).where(PrintQueueItem.id == item_id))).scalar_one()
  77. class TestTheLibraryBulkAdd:
  78. """The route has no per-job toggle at all, so the install-wide default is
  79. the only thing that can decide."""
  80. async def _add(self, async_client: AsyncClient, file_id: int) -> int:
  81. response = await async_client.post("/api/v1/library/files/add-to-queue", json={"file_ids": [file_id]})
  82. assert response.status_code == 200
  83. added = response.json()["added"]
  84. assert len(added) == 1
  85. return added[0]["queue_item_id"]
  86. @pytest.mark.asyncio
  87. async def test_follows_the_default(self, async_client, db_session, test_engine, sliced_library_file):
  88. await _set_setting(db_session, "default_confirm_outcome", "true")
  89. item_id = await self._add(async_client, sliced_library_file.id)
  90. assert (await _read_item(test_engine, item_id)).confirm_outcome is True
  91. @pytest.mark.asyncio
  92. async def test_stays_off_for_an_install_that_never_asked_for_it(
  93. self, async_client, db_session, test_engine, sliced_library_file
  94. ):
  95. item_id = await self._add(async_client, sliced_library_file.id)
  96. assert (await _read_item(test_engine, item_id)).confirm_outcome is False