diff --git a/backend/app/api/routes/archives.py b/backend/app/api/routes/archives.py index ac4bb5810..b517ee211 100644 --- a/backend/app/api/routes/archives.py +++ b/backend/app/api/routes/archives.py @@ -1270,7 +1270,7 @@ async def scan_timelapse( if not matching_file and (archive.started_at or archive.completed_at or archive.created_at): from datetime import datetime, timedelta - archive_start = archive.started_at + _archive_start = archive.started_at archive_end = archive.completed_at or archive.created_at best_match = None best_diff = timedelta(hours=24) diff --git a/backend/app/main.py b/backend/app/main.py index c15fd6ba2..20c86dc06 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -963,7 +963,7 @@ async def on_print_start(printer_id: int, data: dict): existing_archive.failure_reason = "Stale - print likely cancelled or failed without status update" await db.commit() # Fall through to create new archive (don't return) - existing_archive = None # Clear so we don't use stale archive + _existing_archive = None # Clear so we don't use stale archive else: logger.info( f"Skipping duplicate - already have printing archive {existing_archive.id} for {check_name}" @@ -1325,7 +1325,6 @@ async def on_print_start(printer_id: int, data: dict): if not notification_sent: archive_data = {"print_time_seconds": archive.print_time_seconds} await _send_print_start_notification(printer_id, data, archive_data, logger) - notification_sent = True # Extract printable objects for skip object functionality try: diff --git a/backend/app/services/bambu_mqtt.py b/backend/app/services/bambu_mqtt.py index f2477c8ef..89cf09d69 100644 --- a/backend/app/services/bambu_mqtt.py +++ b/backend/app/services/bambu_mqtt.py @@ -1064,7 +1064,7 @@ class BambuMQTTClient: def _update_state(self, data: dict): """Update printer state from message data.""" - previous_state = self.state.state + _previous_state = self.state.state # Update state fields if "gcode_state" in data: @@ -2370,7 +2370,7 @@ class BambuMQTTClient: def _handle_kprofile_response(self, data: dict): """Handle K-profile response from printer.""" response_nozzle = data.get("nozzle_diameter") - response_seq_id = data.get("sequence_id", "?") + _response_seq_id = data.get("sequence_id", "?") filaments = data.get("filaments", []) expected_nozzle = getattr(self, "_expected_kprofile_nozzle", None) has_pending_request = self._pending_kprofile_response is not None diff --git a/backend/app/services/timelapse_processor.py b/backend/app/services/timelapse_processor.py index fa03faf18..5fae64ab2 100644 --- a/backend/app/services/timelapse_processor.py +++ b/backend/app/services/timelapse_processor.py @@ -258,7 +258,8 @@ class TimelapseProcessor: remaining_speed *= 2.0 # Add final atempo for remaining adjustment - if 0.5 <= remaining_speed <= 2.0 and remaining_speed != 1.0: + # After the while loops above, remaining_speed is guaranteed to be in [0.5, 2.0] + if remaining_speed != 1.0: filters.append(f"atempo={remaining_speed:.4f}") return ",".join(filters) diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index 40f487405..ed83cef41 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -6,10 +6,8 @@ import json import logging import os import shutil -import sys import tempfile from collections.abc import AsyncGenerator -from datetime import datetime from pathlib import Path from unittest.mock import AsyncMock, MagicMock, patch diff --git a/backend/tests/integration/test_camera_api.py b/backend/tests/integration/test_camera_api.py index f6d31e3b4..773b06dca 100644 --- a/backend/tests/integration/test_camera_api.py +++ b/backend/tests/integration/test_camera_api.py @@ -3,7 +3,6 @@ Tests the full request/response cycle for /api/v1/printers/{id}/camera/ endpoints. """ -import asyncio from unittest.mock import AsyncMock, MagicMock, patch import pytest diff --git a/backend/tests/integration/test_discovery_api.py b/backend/tests/integration/test_discovery_api.py index 77fe16379..1c4710219 100644 --- a/backend/tests/integration/test_discovery_api.py +++ b/backend/tests/integration/test_discovery_api.py @@ -3,8 +3,6 @@ Tests the full request/response cycle for /api/v1/discovery/ endpoints. """ -from unittest.mock import AsyncMock, patch - import pytest from httpx import AsyncClient diff --git a/backend/tests/integration/test_endpoint_auth.py b/backend/tests/integration/test_endpoint_auth.py index cadde1bdb..414398b67 100644 --- a/backend/tests/integration/test_endpoint_auth.py +++ b/backend/tests/integration/test_endpoint_auth.py @@ -4,7 +4,7 @@ Tests that verify endpoints properly enforce authentication when auth is enabled and allow access when auth is disabled (CVE-2026-25505 fix verification). """ -from unittest.mock import AsyncMock, patch +from unittest.mock import patch import pytest from httpx import AsyncClient diff --git a/backend/tests/integration/test_print_lifecycle.py b/backend/tests/integration/test_print_lifecycle.py index 24dcde112..bc166aa32 100644 --- a/backend/tests/integration/test_print_lifecycle.py +++ b/backend/tests/integration/test_print_lifecycle.py @@ -13,11 +13,9 @@ Full end-to-end tests require the actual database setup. """ import asyncio -from datetime import datetime from unittest.mock import AsyncMock, MagicMock, patch import pytest -from sqlalchemy import select class TestPrintStartLogic: diff --git a/backend/tests/integration/test_printers_api.py b/backend/tests/integration/test_printers_api.py index cc0cd1dd1..498f88fab 100644 --- a/backend/tests/integration/test_printers_api.py +++ b/backend/tests/integration/test_printers_api.py @@ -3,7 +3,7 @@ Tests the full request/response cycle for /api/v1/printers/ endpoints. """ -from unittest.mock import AsyncMock, MagicMock, patch +from unittest.mock import MagicMock, patch import pytest from httpx import AsyncClient diff --git a/backend/tests/integration/test_updates_api.py b/backend/tests/integration/test_updates_api.py index 4daaff14c..e52f28e63 100644 --- a/backend/tests/integration/test_updates_api.py +++ b/backend/tests/integration/test_updates_api.py @@ -1,6 +1,6 @@ """Integration tests for Updates API endpoints.""" -from unittest.mock import AsyncMock, MagicMock, patch +from unittest.mock import AsyncMock, patch import pytest from httpx import AsyncClient diff --git a/backend/tests/unit/services/test_archive_service.py b/backend/tests/unit/services/test_archive_service.py index 51e59e21a..688db05b8 100644 --- a/backend/tests/unit/services/test_archive_service.py +++ b/backend/tests/unit/services/test_archive_service.py @@ -1,9 +1,6 @@ """Unit tests for the archive service.""" from datetime import datetime -from unittest.mock import AsyncMock, MagicMock, patch - -import pytest class TestArchiveServiceHelpers: @@ -364,41 +361,25 @@ class TestThreeMFPlateIndexExtraction: # First priority should be plate_5.png assert thumbnail_paths[0] == "Metadata/plate_5.png" - def test_print_name_enhanced_for_plate_greater_than_1(self): - """Test that print_name is enhanced with plate info for plate > 1.""" - plate_index = 5 - print_name = "Benchy" - - # Logic from archive.py + @staticmethod + def _enhance_print_name(print_name: str, plate_index: int) -> str: + """Apply plate name enhancement logic from archive.py.""" if plate_index and plate_index > 1: if print_name and f"Plate {plate_index}" not in print_name: print_name = f"{print_name} - Plate {plate_index}" + return print_name - assert print_name == "Benchy - Plate 5" + def test_print_name_enhanced_for_plate_greater_than_1(self): + """Test that print_name is enhanced with plate info for plate > 1.""" + assert self._enhance_print_name("Benchy", 5) == "Benchy - Plate 5" def test_print_name_not_enhanced_for_plate_1(self): """Test that print_name is NOT enhanced for plate 1.""" - plate_index = 1 - print_name = "Benchy" - - # Logic from archive.py - if plate_index and plate_index > 1: - if print_name and f"Plate {plate_index}" not in print_name: - print_name = f"{print_name} - Plate {plate_index}" - - assert print_name == "Benchy" # Unchanged for plate 1 + assert self._enhance_print_name("Benchy", 1) == "Benchy" def test_print_name_not_duplicated(self): """Test that plate info is not added if already present in print_name.""" - plate_index = 5 - print_name = "Benchy - Plate 5" - - # Logic from archive.py - if plate_index and plate_index > 1: - if print_name and f"Plate {plate_index}" not in print_name: - print_name = f"{print_name} - Plate {plate_index}" - - assert print_name == "Benchy - Plate 5" # Not duplicated + assert self._enhance_print_name("Benchy - Plate 5", 5) == "Benchy - Plate 5" def test_high_plate_number_extraction(self): """Test extracting high plate numbers (e.g., plate 28).""" diff --git a/backend/tests/unit/services/test_bambu_mqtt.py b/backend/tests/unit/services/test_bambu_mqtt.py index 50e41bf44..f490068ac 100644 --- a/backend/tests/unit/services/test_bambu_mqtt.py +++ b/backend/tests/unit/services/test_bambu_mqtt.py @@ -4,8 +4,6 @@ Tests for the BambuMQTTClient service. These tests focus on timelapse tracking during prints. """ -from unittest.mock import MagicMock, patch - import pytest diff --git a/backend/tests/unit/services/test_external_camera.py b/backend/tests/unit/services/test_external_camera.py index a404a273a..503abaa0f 100644 --- a/backend/tests/unit/services/test_external_camera.py +++ b/backend/tests/unit/services/test_external_camera.py @@ -4,7 +4,7 @@ Tests for the external camera service. These tests cover pure functions and frame parsing logic. """ -from unittest.mock import MagicMock, patch +from unittest.mock import patch import pytest diff --git a/backend/tests/unit/services/test_hms_errors.py b/backend/tests/unit/services/test_hms_errors.py index 02fcb42b8..50f82789d 100644 --- a/backend/tests/unit/services/test_hms_errors.py +++ b/backend/tests/unit/services/test_hms_errors.py @@ -1,7 +1,5 @@ """Tests for HMS error code translations.""" -import pytest - from backend.app.services.hms_errors import HMS_ERROR_DESCRIPTIONS, get_error_description diff --git a/backend/tests/unit/services/test_notification_service.py b/backend/tests/unit/services/test_notification_service.py index 0d3d530ec..1f8eb492a 100644 --- a/backend/tests/unit/services/test_notification_service.py +++ b/backend/tests/unit/services/test_notification_service.py @@ -4,7 +4,6 @@ Tests event-based notifications and toggle behavior. """ import json -from datetime import datetime from unittest.mock import AsyncMock, MagicMock, patch import pytest diff --git a/backend/tests/unit/services/test_plate_detection.py b/backend/tests/unit/services/test_plate_detection.py index 6896b73bd..49862a73b 100644 --- a/backend/tests/unit/services/test_plate_detection.py +++ b/backend/tests/unit/services/test_plate_detection.py @@ -1,7 +1,5 @@ """Unit tests for plate detection service.""" -import tempfile -from pathlib import Path from unittest.mock import MagicMock, patch import pytest @@ -17,7 +15,12 @@ class TestPlateDetectionResult: def test_result_to_dict(self): """Verify PlateDetectionResult.to_dict() returns correct structure.""" with patch.dict("sys.modules", {"cv2": cv2_mock, "numpy": np_mock}): - from backend.app.services.plate_detection import PlateDetectionResult + import importlib + + import backend.app.services.plate_detection as pd_module + + importlib.reload(pd_module) + PlateDetectionResult = pd_module.PlateDetectionResult result = PlateDetectionResult( is_empty=True, @@ -40,7 +43,12 @@ class TestPlateDetectionResult: def test_result_with_debug_image(self): """Verify has_debug_image is True when debug_image is provided.""" with patch.dict("sys.modules", {"cv2": cv2_mock, "numpy": np_mock}): - from backend.app.services.plate_detection import PlateDetectionResult + import importlib + + import backend.app.services.plate_detection as pd_module + + importlib.reload(pd_module) + PlateDetectionResult = pd_module.PlateDetectionResult result = PlateDetectionResult( is_empty=False, @@ -57,7 +65,12 @@ class TestPlateDetectionResult: def test_result_needs_calibration(self): """Verify needs_calibration flag is preserved.""" with patch.dict("sys.modules", {"cv2": cv2_mock, "numpy": np_mock}): - from backend.app.services.plate_detection import PlateDetectionResult + import importlib + + import backend.app.services.plate_detection as pd_module + + importlib.reload(pd_module) + PlateDetectionResult = pd_module.PlateDetectionResult result = PlateDetectionResult( is_empty=True, diff --git a/backend/tests/unit/services/test_printer_manager.py b/backend/tests/unit/services/test_printer_manager.py index bd014c627..51e434207 100644 --- a/backend/tests/unit/services/test_printer_manager.py +++ b/backend/tests/unit/services/test_printer_manager.py @@ -3,9 +3,7 @@ Tests printer connection management, status tracking, and print control. """ -import asyncio -from datetime import datetime -from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch +from unittest.mock import AsyncMock, MagicMock, patch import pytest diff --git a/backend/tests/unit/services/test_smart_plug_manager.py b/backend/tests/unit/services/test_smart_plug_manager.py index 5d66c0da0..c5e23c4ca 100644 --- a/backend/tests/unit/services/test_smart_plug_manager.py +++ b/backend/tests/unit/services/test_smart_plug_manager.py @@ -4,7 +4,6 @@ These tests specifically target the auto-off behavior and toggle functionality that were identified as common regression points. """ -import asyncio from datetime import datetime from unittest.mock import AsyncMock, MagicMock, patch diff --git a/backend/tests/unit/services/test_spoolman_tracking.py b/backend/tests/unit/services/test_spoolman_tracking.py index 7e7467fff..0a854d79d 100644 --- a/backend/tests/unit/services/test_spoolman_tracking.py +++ b/backend/tests/unit/services/test_spoolman_tracking.py @@ -1,7 +1,5 @@ """Unit tests for Spoolman tracking service helpers.""" -import pytest - from backend.app.services.spoolman_tracking import ( _resolve_global_tray_id, _resolve_spool_tag, diff --git a/backend/tests/unit/services/test_stl_thumbnail.py b/backend/tests/unit/services/test_stl_thumbnail.py index 31b6a0a19..22937c090 100644 --- a/backend/tests/unit/services/test_stl_thumbnail.py +++ b/backend/tests/unit/services/test_stl_thumbnail.py @@ -2,7 +2,6 @@ import tempfile from pathlib import Path -from unittest.mock import MagicMock, patch import pytest diff --git a/backend/tests/unit/test_code_quality.py b/backend/tests/unit/test_code_quality.py index d56a53af6..32a3b7aae 100644 --- a/backend/tests/unit/test_code_quality.py +++ b/backend/tests/unit/test_code_quality.py @@ -6,7 +6,6 @@ that could cause runtime errors but aren't caught by normal tests. """ import ast -import os from pathlib import Path import pytest diff --git a/backend/tests/unit/test_log_error_detection.py b/backend/tests/unit/test_log_error_detection.py index 93d0fa865..6793f94b8 100644 --- a/backend/tests/unit/test_log_error_detection.py +++ b/backend/tests/unit/test_log_error_detection.py @@ -5,10 +5,6 @@ These tests use the capture_logs fixture to detect runtime errors that might not cause test failures but indicate problems. """ -from unittest.mock import AsyncMock, MagicMock, patch - -import pytest - class TestMQTTMessageProcessingNoErrors: """Verify MQTT message processing doesn't log errors.""" diff --git a/backend/tests/unit/test_plate_object_extraction.py b/backend/tests/unit/test_plate_object_extraction.py index 303928529..00cf9d817 100644 --- a/backend/tests/unit/test_plate_object_extraction.py +++ b/backend/tests/unit/test_plate_object_extraction.py @@ -1,6 +1,5 @@ """Unit tests for plate object extraction from 3MF model_settings.config.""" -import pytest from defusedxml import ElementTree as ET diff --git a/backend/tests/unit/test_threemf_tools.py b/backend/tests/unit/test_threemf_tools.py index 93008f5d8..b07bd749d 100644 --- a/backend/tests/unit/test_threemf_tools.py +++ b/backend/tests/unit/test_threemf_tools.py @@ -6,8 +6,6 @@ and cumulative layer usage lookup. import math -import pytest - from backend.app.utils.threemf_tools import ( get_cumulative_usage_at_layer, mm_to_grams, diff --git a/tests/e2e_toggle_persistence_test.py b/tests/e2e_toggle_persistence_test.py index d0cf9fe2b..5992c8def 100644 --- a/tests/e2e_toggle_persistence_test.py +++ b/tests/e2e_toggle_persistence_test.py @@ -8,7 +8,7 @@ are properly persisted to the database and survive page reloads. import os import time -from playwright.sync_api import expect, sync_playwright +from playwright.sync_api import sync_playwright BASE_URL = os.environ.get("BAMBUDDY_URL", "http://localhost:8000")