test_kill_switch_stop_archived_cancelled.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. """A print the kill switch stopped is archived as cancelled, with its reason.
  2. The printer reports a stop as "failed" or "aborted", and only stops made from
  3. Bambuddy's queue were turned into "cancelled". A kill-switch stop therefore
  4. went into the failure statistics as a failed print with no reason. It is now
  5. "cancelled" with the reason ``killSwitch``.
  6. """
  7. import asyncio
  8. from types import SimpleNamespace
  9. from unittest.mock import AsyncMock, MagicMock, patch
  10. import pytest
  11. from backend.app import main as main_module
  12. from backend.app.services.archive import ArchiveService
  13. from backend.tests.integration.test_external_print_confirmation_1898 import ( # noqa: F401
  14. _clear_print_state,
  15. _drive_print_start,
  16. )
  17. pytestmark = pytest.mark.integration
  18. @pytest.fixture(autouse=True)
  19. def _clean():
  20. for tracked in (main_module._active_prints, main_module._kill_switch_stopped, main_module._user_stopped_printers):
  21. tracked.clear()
  22. yield
  23. for tracked in (main_module._active_prints, main_module._kill_switch_stopped, main_module._user_stopped_printers):
  24. tracked.clear()
  25. async def _complete(printer_id, status, update):
  26. session = AsyncMock()
  27. session.__aenter__ = AsyncMock(return_value=session)
  28. session.__aexit__ = AsyncMock(return_value=False)
  29. session.execute = AsyncMock(return_value=MagicMock())
  30. tasks_before = set(asyncio.all_tasks())
  31. with (
  32. patch("backend.app.main.async_session", return_value=session),
  33. patch("backend.app.core.database.async_session", return_value=session),
  34. patch("backend.app.main.ws_manager") as ws,
  35. patch("backend.app.main.mqtt_relay") as relay,
  36. patch("backend.app.main.notification_service") as notif,
  37. patch("backend.app.main.smart_plug_manager") as plug,
  38. patch("backend.app.main.printer_manager") as pm,
  39. patch.object(ArchiveService, "update_archive_status", update),
  40. ):
  41. ws.send_print_complete = AsyncMock()
  42. ws.broadcast = AsyncMock()
  43. relay.on_print_complete = AsyncMock()
  44. notif.on_print_complete = AsyncMock()
  45. plug.on_print_complete = AsyncMock()
  46. pm.get_printer.return_value = None
  47. await main_module.on_print_complete(
  48. printer_id,
  49. {"status": status, "filename": "x.gcode", "subtask_name": "Kill_Me", "timelapse_was_active": False},
  50. )
  51. for task in asyncio.all_tasks() - tasks_before:
  52. task.cancel()
  53. try:
  54. await task
  55. except (asyncio.CancelledError, Exception):
  56. pass
  57. @pytest.mark.asyncio
  58. @pytest.mark.parametrize("reported", ["failed", "aborted"])
  59. async def test_a_kill_switch_stop_is_a_cancel_with_its_reason(reported):
  60. main_module._active_prints[(7, "Kill_Me.3mf")] = 55
  61. main_module._kill_switch_stopped.add(7)
  62. update = AsyncMock()
  63. await _complete(7, reported, update)
  64. kwargs = update.await_args.kwargs
  65. assert (kwargs["status"], kwargs["failure_reason"]) == ("cancelled", "killSwitch")
  66. assert 7 not in main_module._kill_switch_stopped
  67. @pytest.mark.asyncio
  68. async def test_a_print_that_failed_on_its_own_stays_failed():
  69. main_module._active_prints[(7, "Kill_Me.3mf")] = 55
  70. update = AsyncMock()
  71. await _complete(7, "failed", update)
  72. assert update.await_args.kwargs["status"] == "failed"
  73. @pytest.mark.asyncio
  74. async def test_the_next_print_starts_without_the_flag(test_engine, printer_factory, monkeypatch):
  75. """A stop whose completion never arrived must not mark the next print."""
  76. monkeypatch.setattr(main_module.printer_manager, "get_current_print_user", lambda printer_id: None)
  77. printer_id = (await printer_factory()).id
  78. main_module._kill_switch_stopped.add(printer_id)
  79. await _drive_print_start(test_engine, SimpleNamespace(id=printer_id), download_ok=False)
  80. assert printer_id not in main_module._kill_switch_stopped
  81. def test_the_reason_is_in_the_vocabulary():
  82. from backend.app.api.routes.print_log import _FAILURE_REASON_KEYS
  83. assert "killSwitch" in _FAILURE_REASON_KEYS
  84. def test_the_archive_editor_offers_the_same_vocabulary():
  85. """A reason the editor does not list shows untranslated, as its own cause."""
  86. import re
  87. from pathlib import Path
  88. from backend.app.api.routes.print_log import _FAILURE_REASON_KEYS
  89. source = (Path(__file__).parents[3] / "frontend/src/components/EditArchiveModal.tsx").read_text()
  90. listed = re.search(r"FAILURE_REASON_KEYS[^=]*=\s*\[(.*?)\]", source, re.S).group(1)
  91. assert set(re.findall(r"'([^']*)'", listed)) == _FAILURE_REASON_KEYS - {""}