| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122 |
- """Every print-start path that binds the print's archive also reads its slicer
- pauses (#2984), like the skip-objects list beside it."""
- import io
- import zipfile
- from contextlib import ExitStack
- from unittest.mock import AsyncMock, MagicMock, patch
- import pytest
- from sqlalchemy import select
- from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
- from backend.app.core.config import settings as app_settings
- from backend.app.models.archive import PrintArchive
- from backend.tests.integration.test_print_start_name_match_ap2 import ( # noqa: F401
- DISPATCH,
- SUBTASK,
- _clear_print_state,
- _start,
- _stuck_archive,
- )
- async def _start_from_cache(test_engine, printer_id: int, cached) -> None:
- """``_start`` with the 3MF already pulled by the cover endpoint (#972)."""
- session_maker = async_sessionmaker(test_engine, class_=AsyncSession, expire_on_commit=False)
- state = MagicMock(sdcard=True, sdcard_reported=True, subtask_id="222", progress=0.0)
- with ExitStack() as stack:
- for target, value in {
- "backend.app.main.async_session": session_maker,
- "backend.app.core.database.async_session": session_maker,
- "backend.app.main.get_cached_3mf": MagicMock(return_value=cached),
- "backend.app.main.cache_3mf_download": MagicMock(),
- "backend.app.main._record_energy_start": AsyncMock(),
- "backend.app.main._send_print_start_notification": AsyncMock(),
- "backend.app.main._maybe_start_layer_timelapse": MagicMock(),
- "backend.app.main._capture_timelapse_baseline_at_start": AsyncMock(),
- "backend.app.main._store_spoolman_print_data": AsyncMock(),
- "backend.app.main._schedule_archive_cost_estimate": MagicMock(),
- "backend.app.services.usage_tracker.on_print_start": AsyncMock(),
- }.items():
- stack.enter_context(patch(target, value))
- for name in ("notification_service", "smart_plug_manager", "ws_manager", "mqtt_relay"):
- mock = stack.enter_context(patch(f"backend.app.main.{name}"))
- for method in (
- "on_print_start",
- "send_print_start",
- "send_archive_created",
- "send_archive_updated",
- "on_archive_created",
- ):
- setattr(mock, method, AsyncMock())
- pm = stack.enter_context(patch("backend.app.main.printer_manager"))
- pm.get_status = MagicMock(return_value=state)
- pm.get_printer = MagicMock(return_value=MagicMock(serial_number="TESTAP2"))
- pm.get_client = MagicMock(return_value=None)
- from backend.app.main import on_print_start
- await on_print_start(
- printer_id, {"filename": DISPATCH, "subtask_name": SUBTASK, "raw_data": {"subtask_id": "222"}}
- )
- @pytest.mark.asyncio
- @pytest.mark.integration
- async def test_resumed_on_its_subtask_id(test_engine, db_session, printer_factory):
- printer = await printer_factory()
- archive_id = await _stuck_archive(db_session, printer.id, "222")
- with patch("backend.app.main._load_pauses_from_3mf") as load:
- await _start(test_engine, printer.id, "222")
- archive = await db_session.get(PrintArchive, archive_id)
- load.assert_called_once()
- assert load.call_args.args == (app_settings.base_dir / archive.file_path, printer.id)
- # As a resumption: what was warned about and passed is kept.
- assert set(load.call_args.kwargs) == {"warned_layers", "passed_through_layer"}
- @pytest.mark.asyncio
- @pytest.mark.integration
- async def test_resumed_by_name(test_engine, db_session, printer_factory):
- printer = await printer_factory()
- archive_id = await _stuck_archive(db_session, printer.id, None)
- with patch("backend.app.main._load_pauses_from_3mf") as load:
- await _start(test_engine, printer.id, "222")
- archive = await db_session.get(PrintArchive, archive_id)
- load.assert_called_once()
- assert load.call_args.args == (app_settings.base_dir / archive.file_path, printer.id)
- # As a resumption: what was warned about and passed is kept.
- assert set(load.call_args.kwargs) == {"warned_layers", "passed_through_layer"}
- @pytest.mark.asyncio
- @pytest.mark.integration
- async def test_a_new_archive_from_the_downloaded_3mf(test_engine, db_session, printer_factory, tmp_path):
- printer = await printer_factory()
- downloaded = tmp_path / f"{SUBTASK}.gcode.3mf"
- buf = io.BytesIO()
- with zipfile.ZipFile(buf, "w") as zf:
- zf.writestr("Metadata/plate_1.gcode", "M73 P0 R10\nM73 L2\nM400 U1\n")
- downloaded.write_bytes(buf.getvalue())
- printer_id = printer.id
- with (
- patch("backend.app.main._load_pauses_from_3mf") as load,
- # The archive is copied under archive_dir; keep it out of the real one.
- patch.object(app_settings, "base_dir", tmp_path),
- patch.object(app_settings, "archive_dir", tmp_path / "archive"),
- ):
- await _start_from_cache(test_engine, printer_id, downloaded)
- expected_base = app_settings.base_dir
- db_session.expire_all()
- archive = (await db_session.execute(select(PrintArchive).where(PrintArchive.printer_id == printer_id))).scalar_one()
- # The archived copy, not the download: that one may be removed meanwhile.
- assert archive.file_path and expected_base / archive.file_path != downloaded
- assert (expected_base / archive.file_path).is_file()
- load.assert_called_once_with(expected_base / archive.file_path, printer_id)
|