| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120 |
- """A print the kill switch stopped is archived as cancelled, with its reason.
- The printer reports a stop as "failed" or "aborted", and only stops made from
- Bambuddy's queue were turned into "cancelled". A kill-switch stop therefore
- went into the failure statistics as a failed print with no reason. It is now
- "cancelled" with the reason ``killSwitch``.
- """
- import asyncio
- from types import SimpleNamespace
- from unittest.mock import AsyncMock, MagicMock, patch
- import pytest
- from backend.app import main as main_module
- from backend.app.services.archive import ArchiveService
- from backend.tests.integration.test_external_print_confirmation_1898 import ( # noqa: F401
- _clear_print_state,
- _drive_print_start,
- )
- pytestmark = pytest.mark.integration
- @pytest.fixture(autouse=True)
- def _clean():
- for tracked in (main_module._active_prints, main_module._kill_switch_stopped, main_module._user_stopped_printers):
- tracked.clear()
- yield
- for tracked in (main_module._active_prints, main_module._kill_switch_stopped, main_module._user_stopped_printers):
- tracked.clear()
- async def _complete(printer_id, status, update):
- session = AsyncMock()
- session.__aenter__ = AsyncMock(return_value=session)
- session.__aexit__ = AsyncMock(return_value=False)
- session.execute = AsyncMock(return_value=MagicMock())
- tasks_before = set(asyncio.all_tasks())
- with (
- patch("backend.app.main.async_session", return_value=session),
- patch("backend.app.core.database.async_session", return_value=session),
- patch("backend.app.main.ws_manager") as ws,
- patch("backend.app.main.mqtt_relay") as relay,
- patch("backend.app.main.notification_service") as notif,
- patch("backend.app.main.smart_plug_manager") as plug,
- patch("backend.app.main.printer_manager") as pm,
- patch.object(ArchiveService, "update_archive_status", update),
- ):
- ws.send_print_complete = AsyncMock()
- ws.broadcast = AsyncMock()
- relay.on_print_complete = AsyncMock()
- notif.on_print_complete = AsyncMock()
- plug.on_print_complete = AsyncMock()
- pm.get_printer.return_value = None
- await main_module.on_print_complete(
- printer_id,
- {"status": status, "filename": "x.gcode", "subtask_name": "Kill_Me", "timelapse_was_active": False},
- )
- for task in asyncio.all_tasks() - tasks_before:
- task.cancel()
- try:
- await task
- except (asyncio.CancelledError, Exception):
- pass
- @pytest.mark.asyncio
- @pytest.mark.parametrize("reported", ["failed", "aborted"])
- async def test_a_kill_switch_stop_is_a_cancel_with_its_reason(reported):
- main_module._active_prints[(7, "Kill_Me.3mf")] = 55
- main_module._kill_switch_stopped.add(7)
- update = AsyncMock()
- await _complete(7, reported, update)
- kwargs = update.await_args.kwargs
- assert (kwargs["status"], kwargs["failure_reason"]) == ("cancelled", "killSwitch")
- assert 7 not in main_module._kill_switch_stopped
- @pytest.mark.asyncio
- async def test_a_print_that_failed_on_its_own_stays_failed():
- main_module._active_prints[(7, "Kill_Me.3mf")] = 55
- update = AsyncMock()
- await _complete(7, "failed", update)
- assert update.await_args.kwargs["status"] == "failed"
- @pytest.mark.asyncio
- async def test_the_next_print_starts_without_the_flag(test_engine, printer_factory, monkeypatch):
- """A stop whose completion never arrived must not mark the next print."""
- monkeypatch.setattr(main_module.printer_manager, "get_current_print_user", lambda printer_id: None)
- printer_id = (await printer_factory()).id
- main_module._kill_switch_stopped.add(printer_id)
- await _drive_print_start(test_engine, SimpleNamespace(id=printer_id), download_ok=False)
- assert printer_id not in main_module._kill_switch_stopped
- def test_the_reason_is_in_the_vocabulary():
- from backend.app.api.routes.print_log import _FAILURE_REASON_KEYS
- assert "killSwitch" in _FAILURE_REASON_KEYS
- def test_the_archive_editor_offers_the_same_vocabulary():
- """A reason the editor does not list shows untranslated, as its own cause."""
- import re
- from pathlib import Path
- from backend.app.api.routes.print_log import _FAILURE_REASON_KEYS
- source = (Path(__file__).parents[3] / "frontend/src/components/EditArchiveModal.tsx").read_text()
- listed = re.search(r"FAILURE_REASON_KEYS[^=]*=\s*\[(.*?)\]", source, re.S).group(1)
- assert set(re.findall(r"'([^']*)'", listed)) == _FAILURE_REASON_KEYS - {""}
|