test_timelapse_scan_newer_run.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. """A timelapse scan attaches only to the run it was started for (TL-2).
  2. The scan polls the printer for minutes after a print ends. When the same
  3. archive starts printing again meanwhile (the next plate of a shared archive, a
  4. reprint), the archive row belongs to the new run: attaching then gave the new
  5. run the old run's video, and the new run's own video was never imported
  6. because the archive already had one.
  7. """
  8. from datetime import datetime
  9. from unittest.mock import AsyncMock
  10. import pytest
  11. from backend.app.models.archive import PrintArchive
  12. from backend.tests.unit.test_fallback_timelapse_baseline_2957 import (
  13. NEW_VIDEO,
  14. OLD_VIDEO,
  15. _entry,
  16. _patches,
  17. _seed,
  18. )
  19. pytestmark = pytest.mark.asyncio
  20. FIRST_RUN = datetime(2026, 10, 19, 8, 0)
  21. NEXT_RUN = datetime(2026, 10, 19, 9, 30)
  22. async def _first_run_ended(maker, archive_id):
  23. async with maker() as db:
  24. archive = await db.get(PrintArchive, archive_id)
  25. archive.started_at = FIRST_RUN
  26. archive.timelapse_baseline = [OLD_VIDEO]
  27. await db.commit()
  28. async def _next_run_starts(maker, archive_id):
  29. async with maker() as db:
  30. archive = await db.get(PrintArchive, archive_id)
  31. archive.started_at = NEXT_RUN
  32. archive.status = "printing"
  33. await db.commit()
  34. async def test_a_run_that_started_during_the_download_does_not_get_the_video(test_engine, tmp_path, monkeypatch):
  35. from backend.app import main as main_module
  36. from backend.app.services import bambu_ftp
  37. maker, _printer_id, archive_id = await _seed(test_engine)
  38. await _first_run_ended(maker, archive_id)
  39. with _patches(main_module, maker, monkeypatch, tmp_path, lambda: [_entry(OLD_VIDEO), _entry(NEW_VIDEO)]):
  40. async def download(*_args, **_kwargs):
  41. await _next_run_starts(maker, archive_id)
  42. return b"x" * 1024
  43. monkeypatch.setattr(bambu_ftp, "download_file_bytes_async", AsyncMock(side_effect=download))
  44. await main_module._scan_for_timelapse_with_retries(archive_id, None)
  45. async with maker() as db:
  46. assert (await db.get(PrintArchive, archive_id)).timelapse_path is None
  47. bambu_ftp.delete_archived_timelapse.assert_not_awaited()
  48. async def test_the_poll_stops_once_a_newer_run_has_started(test_engine, tmp_path, monkeypatch):
  49. from backend.app import main as main_module
  50. from backend.app.services import bambu_ftp
  51. maker, _printer_id, archive_id = await _seed(test_engine)
  52. await _first_run_ended(maker, archive_id)
  53. rounds = []
  54. with _patches(main_module, maker, monkeypatch, tmp_path, lambda: []):
  55. async def listing(_ip, _code, path, printer_model=None):
  56. if path != "/timelapse":
  57. return []
  58. rounds.append(path)
  59. if len(rounds) == 1:
  60. await _next_run_starts(maker, archive_id)
  61. return [_entry(OLD_VIDEO)]
  62. return [_entry(OLD_VIDEO), _entry(NEW_VIDEO)]
  63. monkeypatch.setattr(bambu_ftp, "list_files_async", listing)
  64. await main_module._scan_for_timelapse_with_retries(archive_id, None)
  65. assert len(rounds) == 1, "the poll went on for a run that is not its own"
  66. bambu_ftp.download_file_bytes_async.assert_not_awaited()
  67. async def test_a_video_attached_during_the_download_is_kept(test_engine, tmp_path, monkeypatch):
  68. """Picked by hand (Scan for Timelapse) while the automatic scan downloaded."""
  69. from backend.app import main as main_module
  70. from backend.app.services import bambu_ftp
  71. maker, _printer_id, archive_id = await _seed(test_engine)
  72. await _first_run_ended(maker, archive_id)
  73. with _patches(main_module, maker, monkeypatch, tmp_path, lambda: [_entry(OLD_VIDEO), _entry(NEW_VIDEO)]):
  74. async def download(*_args, **_kwargs):
  75. async with maker() as db:
  76. archive = await db.get(PrintArchive, archive_id)
  77. archive.timelapse_path = "archive/1/picked_by_hand.mp4"
  78. await db.commit()
  79. return b"x" * 1024
  80. monkeypatch.setattr(bambu_ftp, "download_file_bytes_async", AsyncMock(side_effect=download))
  81. await main_module._scan_for_timelapse_with_retries(archive_id, None)
  82. async with maker() as db:
  83. assert (await db.get(PrintArchive, archive_id)).timelapse_path == "archive/1/picked_by_hand.mp4"
  84. bambu_ftp.delete_archived_timelapse.assert_not_awaited()