test_scheduler_force_timelapse_wiring.py 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
  1. """Regression test for the print-queue path of the #1397 force-timelapse fix.
  2. The first round of #1397 only wired the override into ``background_dispatch.py``,
  3. which covers Print Now / Reprint Now flows. The print *queue* uses a separate
  4. scheduler at ``print_scheduler.py:_start_print`` that calls
  5. ``printer_manager.start_print`` directly — and the first attempt skipped that
  6. call site, so queued prints' timelapse setting passed through unchanged and
  7. the finish-photo path had nothing to draw from. Field-test caught this when
  8. Martin queued two prints (H2D + X1C); neither got a forced timelapse and
  9. ``archive.bambuddy_forced_timelapse`` stayed False on both.
  10. This test pins the wiring at the source level: the helper is imported AND
  11. its return value is what ``start_print(timelapse=...)`` receives. We can't
  12. exercise the full ``_start_print`` method without standing up a real DB +
  13. printer_manager + ams_assignment fixture stack, but the structural assert
  14. is enough to catch regression at the dispatch hook.
  15. """
  16. import ast
  17. from pathlib import Path
  18. SCHEDULER_PATH = Path(__file__).resolve().parent.parent.parent / "app" / "services" / "print_scheduler.py"
  19. def _find_call_to_start_print(tree: ast.AST) -> ast.Call:
  20. """Walk the AST and return the printer_manager.start_print(...) Call node
  21. inside _start_print. Should be exactly one."""
  22. for node in ast.walk(tree):
  23. if not isinstance(node, ast.Call):
  24. continue
  25. func = node.func
  26. if not isinstance(func, ast.Attribute):
  27. continue
  28. if func.attr != "start_print":
  29. continue
  30. value = func.value
  31. if not isinstance(value, ast.Name) or value.id != "printer_manager":
  32. continue
  33. return node
  34. raise AssertionError("Could not find printer_manager.start_print(...) call in print_scheduler.py")
  35. def test_start_print_timelapse_kwarg_uses_resolved_value():
  36. """``timelapse=`` kwarg passed to start_print must reference
  37. ``effective_timelapse`` (the resolved value) — not ``item.timelapse``
  38. (the user's raw choice). If a refactor drops the resolver call and
  39. restores ``item.timelapse``, this test fails."""
  40. source = SCHEDULER_PATH.read_text()
  41. tree = ast.parse(source)
  42. call = _find_call_to_start_print(tree)
  43. timelapse_kw = next((kw for kw in call.keywords if kw.arg == "timelapse"), None)
  44. assert timelapse_kw is not None, "start_print(timelapse=...) kwarg is missing"
  45. # The value must be the resolved variable, not item.timelapse.
  46. value = timelapse_kw.value
  47. assert isinstance(value, ast.Name) and value.id == "effective_timelapse", (
  48. f"timelapse= must be the resolver's return value (effective_timelapse), "
  49. f"got {ast.dump(value)}. The queue path must apply the same #1397 "
  50. f"override as background_dispatch.py — otherwise queued prints' "
  51. f"finish-photo extractor has nothing to pull from."
  52. )
  53. def test_scheduler_imports_resolve_effective_timelapse():
  54. """The import must exist somewhere in print_scheduler.py — guards against
  55. a future refactor removing it and falling back to item.timelapse."""
  56. source = SCHEDULER_PATH.read_text()
  57. assert "from backend.app.services.background_dispatch import resolve_effective_timelapse" in source, (
  58. "print_scheduler.py must import resolve_effective_timelapse from background_dispatch"
  59. )