test_ftp_bytes_download_deadline_3272.py 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. """The whole-file bytes download gives a big file time to arrive (#3272).
  2. A flat 300 s cap failed a 75 MB P1S timelapse that took 358 s on a healthy
  3. link, and every retry started the file again from scratch. The deadline now
  4. follows the listing's size at the pessimistic floor rate, with no ceiling: the
  5. callers that pass a size hold no DB connection across the transfer.
  6. """
  7. import logging
  8. from unittest.mock import MagicMock, patch
  9. import pytest
  10. from backend.app.services import bambu_ftp
  11. IP = "192.0.2.10"
  12. async def _deadline_for(**kwargs) -> float:
  13. """Run the download with a client that never connects; return its deadline."""
  14. seen: list[float] = []
  15. real_wait_for = bambu_ftp.asyncio.wait_for
  16. async def recording_wait_for(aw, timeout):
  17. seen.append(timeout)
  18. return await real_wait_for(aw, timeout)
  19. client = MagicMock()
  20. client.connect.return_value = False
  21. with (
  22. patch.object(bambu_ftp, "BambuFTPClient", return_value=client),
  23. patch.object(bambu_ftp.asyncio, "wait_for", recording_wait_for),
  24. ):
  25. assert await bambu_ftp.download_file_bytes_async(IP, "12345678", "/timelapse/a.avi", **kwargs) is None
  26. assert len(seen) == 1
  27. return seen[0]
  28. @pytest.mark.asyncio
  29. async def test_unknown_size_keeps_default_deadline():
  30. assert await _deadline_for() == 300.0
  31. @pytest.mark.asyncio
  32. async def test_small_file_keeps_default_deadline():
  33. assert await _deadline_for(expected_size=2 * 1024 * 1024) == 300.0
  34. @pytest.mark.asyncio
  35. async def test_large_file_deadline_follows_size_without_ceiling():
  36. # The reporter's largest timelapse; at the floor rate it needs ~94 minutes.
  37. size = 143_788_322
  38. deadline = await _deadline_for(expected_size=size)
  39. assert deadline == size / bambu_ftp._DOWNLOAD_FLOOR_BYTES_PER_SEC
  40. assert deadline > bambu_ftp._DOWNLOAD_MAX_TIMEOUT
  41. @pytest.mark.asyncio
  42. async def test_explicit_longer_timeout_is_never_shortened():
  43. assert await _deadline_for(expected_size=1024, timeout=900.0) == 900.0
  44. @pytest.mark.asyncio
  45. async def test_cap_log_names_file_and_size(caplog):
  46. async def expire(aw, timeout):
  47. # Let the worker finish while the client is still patched, so no real
  48. # connection is attempted after the patch is gone.
  49. await aw
  50. raise TimeoutError
  51. client = MagicMock()
  52. client.connect.return_value = False
  53. with (
  54. patch.object(bambu_ftp, "BambuFTPClient", return_value=client),
  55. patch.object(bambu_ftp.asyncio, "wait_for", expire),
  56. caplog.at_level(logging.WARNING, logger=bambu_ftp.logger.name),
  57. ):
  58. result = await bambu_ftp.download_file_bytes_async(IP, "12345678", "/timelapse/a.avi", expected_size=75_647_046)
  59. assert result is None
  60. assert "/timelapse/a.avi (75647046 bytes) exceeded its 2955s cap" in caplog.text