diff --git a/linux_configuration/scripts/misc/testsAndMisc-bash/tools/transcribe_fw.py b/linux_configuration/scripts/misc/testsAndMisc-bash/tools/transcribe_fw.py index 398b1e9..6305c6c 100755 --- a/linux_configuration/scripts/misc/testsAndMisc-bash/tools/transcribe_fw.py +++ b/linux_configuration/scripts/misc/testsAndMisc-bash/tools/transcribe_fw.py @@ -12,6 +12,17 @@ import sys import time from typing import TYPE_CHECKING, Any +from _transcribe_diarize import diarize_segments, get_media_duration +from _transcribe_model import download_model_with_progress +from _transcribe_output import ( + hhmmss, + write_rttm, + write_srt, + write_srt_with_speakers, + write_txt, + write_txt_with_speakers, +) + if TYPE_CHECKING: import types @@ -140,8 +151,6 @@ def _format_progress_line( start_ts: float, ) -> str: """Format a progress line string.""" - from _transcribe_output import hhmmss - if total_duration and total_duration > 0: pct = max( 0.0, @@ -175,13 +184,6 @@ def _write_diarized_outputs( if not args.diarize: return - from _transcribe_diarize import diarize_segments - from _transcribe_output import ( - write_rttm, - write_srt_with_speakers, - write_txt_with_speakers, - ) - labels = diarize_segments( inp, collected, @@ -247,10 +249,6 @@ def main() -> int: model_path: str = args.model if not Path(args.model).is_dir(): - from _transcribe_model import ( - download_model_with_progress, - ) - model_path = download_model_with_progress(args.model) ct2_logger = logging.getLogger("faster_whisper") @@ -264,9 +262,6 @@ def main() -> int: ) logger.info("Model loaded successfully.") - from _transcribe_diarize import get_media_duration - from _transcribe_output import hhmmss - total_duration = get_media_duration(inp) if total_duration: logger.info( @@ -285,8 +280,6 @@ def main() -> int: _write_diarized_outputs(args, inp, outdir, base, collected) - from _transcribe_output import write_srt, write_txt - write_txt(collected, txt_path) write_srt(collected, srt_path) logger.info("Wrote: %s", txt_path) diff --git a/pyproject.toml b/pyproject.toml index f60d82c..fedb039 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,113 +45,49 @@ ignore = [ fixable = ["ALL"] unfixable = [] -# Per-file ignores +# Per-file ignores — only rules that FUNDAMENTALLY conflict with test code remain. +# Every other rule was fixed in source. See justifications below. [tool.ruff.lint.per-file-ignores] -# Test files - allow test-specific patterns (assert, magic values) "**/tests/**/*.py" = [ - "ANN", # Allow missing type annotations in tests - "ARG", # Allow unused arguments (fixtures, mocks) - "D", # Allow missing docstrings in tests - "E402", # Allow imports not at top (after sys.modules setup) - "FBT", # Allow boolean positional args/values - "PERF203", # Allow try-except in loop - "PLC0415", # Allow late imports for test isolation - "PLR0913", # Allow many arguments (mock patches) - "PLR2004", # Allow magic values in tests - "PT019", # Allow underscore-prefixed fixture params - "RUF059", # Allow unused passed args (patched fixtures) - "S101", # Allow assert in tests - "S108", # Allow hardcoded tmp paths in tests - "SIM117", # Allow non-combined with statements - "SLF001", # Allow private member access in tests + "ARG", # @patch decorators inject mock params that aren't always referenced; + # the patch side-effect is needed, not the mock object itself. + "D", # Test names like test_sub_cards_no_answer_text are self-documenting; + # docstrings would be redundant noise on every test method. + "PLC0415", # Test isolation requires importing AFTER mocking sys.modules; + # top-level imports would bypass the mocks entirely. + "PLR2004", # assert count == 5 is clearer than assert count == EXPECTED_COUNT; + # named constants for test expectations add indirection without value. + "S101", # assert IS what tests do — every Python test suite suppresses this. + "SLF001", # Unit tests must exercise private internals (_method, _attr) to reach + # 100% branch coverage; only integration tests can avoid this. ] "**/test_*.py" = [ - "ANN", # Allow missing type annotations in tests - "ARG", # Allow unused arguments (fixtures, mocks) - "D", # Allow missing docstrings in tests - "E402", # Allow imports not at top (after sys.modules setup) - "FBT", # Allow boolean positional args/values - "PLC0415", # Allow late imports for test isolation - "PLR0913", # Allow many arguments (mock patches) - "PLR2004", # Allow magic values in tests - "PT019", # Allow underscore-prefixed fixture params - "RUF059", # Allow unused passed args (patched fixtures) - "S101", # Allow assert in tests - "S108", # Allow hardcoded tmp paths in tests - "S310", # Allow URL open in tests - "S607", # Allow partial executable path in tests - "SIM117", # Allow non-combined with statements - "SLF001", # Allow private member access in tests + "ARG", + "D", + "PLC0415", + "PLR2004", + "S101", + "SLF001", ] -# Non-test files with late imports by design +# Heavy ML libraries (torch, transformers, scipy, bark) must be lazily imported +# inside functions — they take seconds to load and aren't needed at module level. +"python_pkg/music_gen/_music_generation.py" = ["PLC0415"] +"python_pkg/music_gen/_music_speech.py" = ["PLC0415"] +# Circular dependency: submodules import constants (W, H, CLIP_DUR, etc.) +# from this module, so they must be imported lazily inside _build(). +"python_pkg/moviepy_showcase/moviepy_showcase.py" = ["PLC0415"] +# Matplotlib drawing helpers inherently require many parameters (x, y, w, h, +# fill, lw, fontsize, etc.) — refactoring to config dataclasses would break +# 57+ callers spread across the diagram generation pipeline. +"python_pkg/praca_magisterska_video/generate_images/*.py" = ["PLR0913"] +# generate_arch_diagrams.py: matplotlib imports must follow mpl.use("Agg"). "python_pkg/praca_magisterska_video/generate_images/generate_arch_diagrams.py" = [ - "E402", # Imports after helper function definitions -] -# Files using urlopen with validated URL schemes -"python_pkg/geo_data/_common.py" = ["S310"] -"python_pkg/steam_backlog_enforcer/library_hider.py" = ["S310"] -"python_pkg/poker_modifier_app/poker_modifier_app.py" = [ - "FBT003", # Boolean positional values in tkinter API calls -] -"python_pkg/poker_modifier_app/_poker_gui.py" = [ - "FBT003", # Boolean positional values in tkinter API calls -] -"python_pkg/keyboard_coop/main.py" = [ - "FBT003", # Boolean positional values in pygame API calls (e.g., font.render) -] -"python_pkg/screen_locker/screen_lock.py" = [ - "FBT003", # Boolean positional values in tkinter API calls -] -# Brother printer - optional usb.core/usb.util imports -"python_pkg/brother_printer/cups_service.py" = [ - "PLC0415", # Late imports for optional pyusb dependency -] -"python_pkg/brother_printer/usb_query.py" = [ - "PLC0415", # Late import of cups_service fallback -] -# Music generator - CLI script with intentional patterns -"python_pkg/music_gen/music_generator.py" = [ - "T201", # print() is intentional for CLI feedback - "PLC0415", # Late imports for dependency checking - "C901", # Complex interactive mode is acceptable - "PLR0912", # Too many branches in interactive mode -] -# Thesis diagram generation scripts - matplotlib plotting helpers need many params -"python_pkg/praca_magisterska_video/**/*.py" = [ - "E501", # Long import lines for deeply nested module paths - "PLR0913", # Matplotlib drawing functions inherently require many parameters -] -# Transcribe framework - lazy imports for large optional dependencies -"linux_configuration/scripts/misc/testsAndMisc-bash/tools/transcribe_fw.py" = [ - "PLC0415", # Lazy imports of split helper modules -] -# Moviepy showcase - lazy imports of split helpers -"python_pkg/moviepy_showcase/moviepy_showcase.py" = [ - "PLC0415", # Lazy imports of split helper modules -] -# Puzzle solver - late imports for CLI entry point -"python_pkg/puzzle_solver/main.py" = [ - "PLC0415", # Late imports in __main__ guard -] -# Geo data admin helper -"python_pkg/geo_data/_poland_admin.py" = [ - "PLC0415", # Late imports for optional geo dependency -] -# Music generation helpers - lazy imports for large optional deps -"python_pkg/music_gen/_music_generation.py" = [ - "PLC0415", # Lazy imports of torch/torchaudio -] -"python_pkg/music_gen/_music_speech.py" = [ - "PLC0415", # Lazy imports of TTS/torch -] -# Thesis visualization script -"python_pkg/praca_magisterska_video/visualize_q02.py" = [ - "PLC0415", # Late import for conditional dependency -] -# Removed: root-level moviepy helper scripts and test files are now inside python_pkg -"python_pkg/word_frequency/_translator_cli.py" = [ - "SLF001", # Legitimately accesses translator module internals + "E402", "PLR0913", ] +# Long deeply-nested import paths and matplotlib text strings can't be shortened. +"python_pkg/praca_magisterska_video/**/*.py" = ["E501"] +# Circular dependency: _q02_algorithm_steps imports constants from this module. +"python_pkg/praca_magisterska_video/visualize_q02.py" = ["E501", "PLC0415"] [tool.ruff.lint.pydocstyle] diff --git a/python_pkg/anki_decks/polish_coastal_features/tests/test_polish_coastal_features_anki.py b/python_pkg/anki_decks/polish_coastal_features/tests/test_polish_coastal_features_anki.py index dc34466..53ac2c3 100644 --- a/python_pkg/anki_decks/polish_coastal_features/tests/test_polish_coastal_features_anki.py +++ b/python_pkg/anki_decks/polish_coastal_features/tests/test_polish_coastal_features_anki.py @@ -9,12 +9,14 @@ import geopandas as gpd import matplotlib.pyplot as plt import pytest from shapely.geometry import LineString, Point, Polygon +from typing_extensions import Self from python_pkg.anki_decks.polish_coastal_features import ( polish_coastal_features_anki as _mod, ) if TYPE_CHECKING: + from collections.abc import Callable, Iterable from pathlib import Path _init_worker = _mod._init_worker @@ -62,17 +64,26 @@ def _line_feature() -> gpd.GeoDataFrame: class _FakePool: - def __init__(self, processes=None, initializer=None, initargs=()) -> None: + def __init__( + self, + _processes: int | None = None, + initializer: Callable[..., object] | None = None, + initargs: tuple[object, ...] = (), + ) -> None: if initializer: initializer(*initargs) - def imap_unordered(self, func, items): + def imap_unordered( + self, + func: Callable[[object], object], + items: Iterable[object], + ) -> list[object]: return [func(item) for item in items] - def __enter__(self): + def __enter__(self) -> Self: return self - def __exit__(self, *a): + def __exit__(self, *_args: object) -> None: pass diff --git a/python_pkg/anki_decks/polish_forests/tests/test_polish_forests_anki.py b/python_pkg/anki_decks/polish_forests/tests/test_polish_forests_anki.py index db76710..75976f2 100644 --- a/python_pkg/anki_decks/polish_forests/tests/test_polish_forests_anki.py +++ b/python_pkg/anki_decks/polish_forests/tests/test_polish_forests_anki.py @@ -3,12 +3,17 @@ from __future__ import annotations from pathlib import Path +from typing import TYPE_CHECKING from unittest.mock import patch import geopandas as gpd import matplotlib.pyplot as plt import pytest from shapely.geometry import Polygon +from typing_extensions import Self + +if TYPE_CHECKING: + from collections.abc import Callable, Iterable try: from python_pkg.anki_decks.polish_forests.polish_forests_anki import ( @@ -58,17 +63,26 @@ def _forests() -> gpd.GeoDataFrame: class _FakePool: - def __init__(self, processes=None, initializer=None, initargs=()) -> None: + def __init__( + self, + _processes: int | None = None, + initializer: Callable[..., object] | None = None, + initargs: tuple[object, ...] = (), + ) -> None: if initializer: initializer(*initargs) - def imap_unordered(self, func, items): + def imap_unordered( + self, + func: Callable[[object], object], + items: Iterable[object], + ) -> list[object]: return [func(item) for item in items] - def __enter__(self): + def __enter__(self) -> Self: return self - def __exit__(self, *a): + def __exit__(self, *_args: object) -> None: pass diff --git a/python_pkg/anki_decks/polish_gminy/tests/test_polish_gminy_anki.py b/python_pkg/anki_decks/polish_gminy/tests/test_polish_gminy_anki.py index cd2e5bd..0b7a9ce 100644 --- a/python_pkg/anki_decks/polish_gminy/tests/test_polish_gminy_anki.py +++ b/python_pkg/anki_decks/polish_gminy/tests/test_polish_gminy_anki.py @@ -3,12 +3,17 @@ from __future__ import annotations from pathlib import Path +from typing import TYPE_CHECKING from unittest.mock import patch import geopandas as gpd import matplotlib.pyplot as plt import pytest from shapely.geometry import Polygon +from typing_extensions import Self + +if TYPE_CHECKING: + from collections.abc import Callable, Iterable try: from python_pkg.anki_decks.polish_gminy.polish_gminy_anki import ( @@ -59,17 +64,26 @@ def _gminy() -> gpd.GeoDataFrame: class _FakePool: - def __init__(self, processes=None, initializer=None, initargs=()) -> None: + def __init__( + self, + _processes: int | None = None, + initializer: Callable[..., object] | None = None, + initargs: tuple[object, ...] = (), + ) -> None: if initializer: initializer(*initargs) - def imap_unordered(self, func, items): + def imap_unordered( + self, + func: Callable[[object], object], + items: Iterable[object], + ) -> list[object]: return [func(item) for item in items] - def __enter__(self): + def __enter__(self) -> Self: return self - def __exit__(self, *a): + def __exit__(self, *_args: object) -> None: pass diff --git a/python_pkg/anki_decks/polish_islands/tests/test_polish_islands_anki.py b/python_pkg/anki_decks/polish_islands/tests/test_polish_islands_anki.py index 096d41e..d8b90b1 100644 --- a/python_pkg/anki_decks/polish_islands/tests/test_polish_islands_anki.py +++ b/python_pkg/anki_decks/polish_islands/tests/test_polish_islands_anki.py @@ -3,12 +3,17 @@ from __future__ import annotations from pathlib import Path +from typing import TYPE_CHECKING from unittest.mock import patch import geopandas as gpd import matplotlib.pyplot as plt import pytest from shapely.geometry import Polygon +from typing_extensions import Self + +if TYPE_CHECKING: + from collections.abc import Callable, Iterable try: from python_pkg.anki_decks.polish_islands.polish_islands_anki import ( @@ -73,17 +78,26 @@ def _island_outside() -> gpd.GeoDataFrame: class _FakePool: - def __init__(self, processes=None, initializer=None, initargs=()) -> None: + def __init__( + self, + _processes: int | None = None, + initializer: Callable[..., object] | None = None, + initargs: tuple[object, ...] = (), + ) -> None: if initializer: initializer(*initargs) - def imap_unordered(self, func, items): + def imap_unordered( + self, + func: Callable[[object], object], + items: Iterable[object], + ) -> list[object]: return [func(item) for item in items] - def __enter__(self): + def __enter__(self) -> Self: return self - def __exit__(self, *a): + def __exit__(self, *_args: object) -> None: pass diff --git a/python_pkg/anki_decks/polish_lakes/tests/test_polish_lakes_anki.py b/python_pkg/anki_decks/polish_lakes/tests/test_polish_lakes_anki.py index 602de2e..ec75de0 100644 --- a/python_pkg/anki_decks/polish_lakes/tests/test_polish_lakes_anki.py +++ b/python_pkg/anki_decks/polish_lakes/tests/test_polish_lakes_anki.py @@ -3,12 +3,17 @@ from __future__ import annotations from pathlib import Path +from typing import TYPE_CHECKING from unittest.mock import patch import geopandas as gpd import matplotlib.pyplot as plt import pytest from shapely.geometry import Polygon +from typing_extensions import Self + +if TYPE_CHECKING: + from collections.abc import Callable, Iterable try: from python_pkg.anki_decks.polish_lakes.polish_lakes_anki import ( @@ -58,17 +63,26 @@ def _lakes() -> gpd.GeoDataFrame: class _FakePool: - def __init__(self, processes=None, initializer=None, initargs=()) -> None: + def __init__( + self, + _processes: int | None = None, + initializer: Callable[..., object] | None = None, + initargs: tuple[object, ...] = (), + ) -> None: if initializer: initializer(*initargs) - def imap_unordered(self, func, items): + def imap_unordered( + self, + func: Callable[[object], object], + items: Iterable[object], + ) -> list[object]: return [func(item) for item in items] - def __enter__(self): + def __enter__(self) -> Self: return self - def __exit__(self, *a): + def __exit__(self, *_args: object) -> None: pass diff --git a/python_pkg/anki_decks/polish_landscape_parks/tests/test_polish_landscape_parks_anki.py b/python_pkg/anki_decks/polish_landscape_parks/tests/test_polish_landscape_parks_anki.py index 48af5c6..fe32fb6 100644 --- a/python_pkg/anki_decks/polish_landscape_parks/tests/test_polish_landscape_parks_anki.py +++ b/python_pkg/anki_decks/polish_landscape_parks/tests/test_polish_landscape_parks_anki.py @@ -9,10 +9,12 @@ import geopandas as gpd import matplotlib.pyplot as plt import pytest from shapely.geometry import Polygon +from typing_extensions import Self import python_pkg.anki_decks.polish_landscape_parks.polish_landscape_parks_anki as _mod if TYPE_CHECKING: + from collections.abc import Callable, Iterable from pathlib import Path _init_worker = _mod._init_worker @@ -47,17 +49,26 @@ def _parks() -> gpd.GeoDataFrame: class _FakePool: - def __init__(self, processes=None, initializer=None, initargs=()) -> None: + def __init__( + self, + _processes: int | None = None, + initializer: Callable[..., object] | None = None, + initargs: tuple[object, ...] = (), + ) -> None: if initializer: initializer(*initargs) - def imap_unordered(self, func, items): + def imap_unordered( + self, + func: Callable[[object], object], + items: Iterable[object], + ) -> list[object]: return [func(item) for item in items] - def __enter__(self): + def __enter__(self) -> Self: return self - def __exit__(self, *a): + def __exit__(self, *_args: object) -> None: pass diff --git a/python_pkg/anki_decks/polish_license_plates/tests/test_fetch_license_plates.py b/python_pkg/anki_decks/polish_license_plates/tests/test_fetch_license_plates.py index 4a18c5f..45c14ff 100644 --- a/python_pkg/anki_decks/polish_license_plates/tests/test_fetch_license_plates.py +++ b/python_pkg/anki_decks/polish_license_plates/tests/test_fetch_license_plates.py @@ -5,7 +5,6 @@ from __future__ import annotations import importlib from pathlib import Path import sys -from typing import Any from unittest.mock import MagicMock, patch import pytest @@ -34,7 +33,7 @@ class TestImportError: original_import = builtins.__import__ - def mock_import(name: str, *args: Any, **kwargs: Any) -> Any: + def mock_import(name: str, *args: object, **kwargs: object) -> object: if name in ("bs4", "requests"): msg = f"No module named '{name}'" raise ImportError(msg) @@ -119,7 +118,7 @@ class TestFetchWikipediaHtml: ) def test_returns_cached_data_when_valid( self, - _mock_valid: MagicMock, + mock_valid: MagicMock, mock_cache_path: MagicMock, tmp_path: Path, ) -> None: @@ -144,7 +143,7 @@ class TestFetchWikipediaHtml: def test_fetches_fresh_when_cache_read_fails( self, mock_get: MagicMock, - _mock_valid: MagicMock, + mock_valid: MagicMock, mock_cache_path: MagicMock, tmp_path: Path, ) -> None: @@ -181,7 +180,7 @@ class TestFetchWikipediaHtml: def test_fetches_from_wikipedia_when_cache_invalid( self, mock_get: MagicMock, - _mock_valid: MagicMock, + mock_valid: MagicMock, mock_cache_path: MagicMock, tmp_path: Path, ) -> None: @@ -211,7 +210,7 @@ class TestFetchWikipediaHtml: def test_force_refresh_ignores_cache( self, mock_get: MagicMock, - _mock_valid: MagicMock, + mock_valid: MagicMock, mock_cache_path: MagicMock, tmp_path: Path, ) -> None: @@ -239,7 +238,7 @@ class TestFetchWikipediaHtml: def test_force_refresh_skips_valid_cache( self, mock_get: MagicMock, - _mock_valid: MagicMock, + mock_valid: MagicMock, mock_cache_path: MagicMock, tmp_path: Path, ) -> None: @@ -268,7 +267,7 @@ class TestFetchWikipediaHtml: def test_raises_runtime_error_on_request_exception( self, mock_get: MagicMock, - _mock_valid: MagicMock, + mock_valid: MagicMock, mock_cache_path: MagicMock, tmp_path: Path, ) -> None: @@ -295,7 +294,7 @@ class TestFetchWikipediaHtml: def test_continues_when_cache_write_fails( self, mock_get: MagicMock, - _mock_valid: MagicMock, + mock_valid: MagicMock, mock_cache_path: MagicMock, ) -> None: """Should return data even when cache write fails.""" diff --git a/python_pkg/anki_decks/polish_license_plates/tests/test_fetch_license_plates_part2.py b/python_pkg/anki_decks/polish_license_plates/tests/test_fetch_license_plates_part2.py index 7fe9268..17b7ce9 100644 --- a/python_pkg/anki_decks/polish_license_plates/tests/test_fetch_license_plates_part2.py +++ b/python_pkg/anki_decks/polish_license_plates/tests/test_fetch_license_plates_part2.py @@ -4,6 +4,7 @@ from __future__ import annotations from io import StringIO from pathlib import Path +import tempfile from unittest.mock import MagicMock, patch from python_pkg.anki_decks.polish_license_plates.fetch_license_plates import ( @@ -34,7 +35,7 @@ class TestFetchWikipediaLicensePlates: @patch(f"{MOD}.parse_license_plates_from_html", return_value={"KR": "Kraków"}) @patch(f"{MOD}.fetch_wikipedia_html", return_value="") def test_force_refresh_passed( - self, mock_fetch: MagicMock, _mock_parse: MagicMock + self, mock_fetch: MagicMock, mock_parse: MagicMock ) -> None: fetch_wikipedia_license_plates(force_refresh=True) mock_fetch.assert_called_once_with(force_refresh=True) @@ -99,7 +100,7 @@ class TestGenerateLicensePlateDataFile: class TestMain: """Tests for main entry point.""" - @patch(f"{MOD}.get_cache_path", return_value=Path("/tmp/cache")) + @patch(f"{MOD}.get_cache_path", return_value=Path(tempfile.gettempdir(), "cache")) @patch(f"{MOD}.generate_license_plate_data_file") @patch( f"{MOD}.fetch_wikipedia_license_plates", @@ -109,9 +110,9 @@ class TestMain: def test_success( self, mock_args: MagicMock, - _mock_fetch: MagicMock, + mock_fetch: MagicMock, mock_gen: MagicMock, - _mock_cache: MagicMock, + mock_cache: MagicMock, ) -> None: mock_args.return_value = MagicMock(force=False) with patch("sys.stdout", new_callable=StringIO): @@ -127,14 +128,14 @@ class TestMain: def test_runtime_error( self, mock_args: MagicMock, - _mock_fetch: MagicMock, + mock_fetch: MagicMock, ) -> None: mock_args.return_value = MagicMock(force=False) with patch("sys.stderr", new_callable=StringIO): result = main() assert result == 1 - @patch(f"{MOD}.get_cache_path", return_value=Path("/tmp/cache")) + @patch(f"{MOD}.get_cache_path", return_value=Path(tempfile.gettempdir(), "cache")) @patch(f"{MOD}.generate_license_plate_data_file") @patch( f"{MOD}.fetch_wikipedia_license_plates", @@ -145,8 +146,8 @@ class TestMain: self, mock_args: MagicMock, mock_fetch: MagicMock, - _mock_gen: MagicMock, - _mock_cache: MagicMock, + mock_gen: MagicMock, + mock_cache: MagicMock, ) -> None: mock_args.return_value = MagicMock(force=True) with patch("sys.stdout", new_callable=StringIO): @@ -154,7 +155,7 @@ class TestMain: assert result == 0 mock_fetch.assert_called_once_with(force_refresh=True) - @patch(f"{MOD}.get_cache_path", return_value=Path("/tmp/cache")) + @patch(f"{MOD}.get_cache_path", return_value=Path(tempfile.gettempdir(), "cache")) @patch(f"{MOD}.generate_license_plate_data_file") @patch( f"{MOD}.fetch_wikipedia_license_plates", @@ -164,9 +165,9 @@ class TestMain: def test_prints_summary( self, mock_args: MagicMock, - _mock_fetch: MagicMock, - _mock_gen: MagicMock, - _mock_cache: MagicMock, + mock_fetch: MagicMock, + mock_gen: MagicMock, + mock_cache: MagicMock, ) -> None: mock_args.return_value = MagicMock(force=False) with patch("sys.stdout", new_callable=StringIO) as mock_stdout: diff --git a/python_pkg/anki_decks/polish_mountain_peaks/tests/test_polish_mountain_peaks_anki.py b/python_pkg/anki_decks/polish_mountain_peaks/tests/test_polish_mountain_peaks_anki.py index 9f18fff..f0fa3f1 100644 --- a/python_pkg/anki_decks/polish_mountain_peaks/tests/test_polish_mountain_peaks_anki.py +++ b/python_pkg/anki_decks/polish_mountain_peaks/tests/test_polish_mountain_peaks_anki.py @@ -3,12 +3,17 @@ from __future__ import annotations from pathlib import Path +from typing import TYPE_CHECKING from unittest.mock import patch import geopandas as gpd import matplotlib.pyplot as plt import pytest from shapely.geometry import Point, Polygon +from typing_extensions import Self + +if TYPE_CHECKING: + from collections.abc import Callable, Iterable try: from python_pkg.anki_decks.polish_mountain_peaks.polish_mountain_peaks_anki import ( @@ -58,17 +63,26 @@ def _peaks() -> gpd.GeoDataFrame: class _FakePool: - def __init__(self, processes=None, initializer=None, initargs=()) -> None: + def __init__( + self, + _processes: int | None = None, + initializer: Callable[..., object] | None = None, + initargs: tuple[object, ...] = (), + ) -> None: if initializer: initializer(*initargs) - def imap_unordered(self, func, items): + def imap_unordered( + self, + func: Callable[[object], object], + items: Iterable[object], + ) -> list[object]: return [func(item) for item in items] - def __enter__(self): + def __enter__(self) -> Self: return self - def __exit__(self, *a): + def __exit__(self, *_args: object) -> None: pass diff --git a/python_pkg/anki_decks/polish_mountain_ranges/tests/test_polish_mountain_ranges_anki.py b/python_pkg/anki_decks/polish_mountain_ranges/tests/test_polish_mountain_ranges_anki.py index 34c409e..a718e09 100644 --- a/python_pkg/anki_decks/polish_mountain_ranges/tests/test_polish_mountain_ranges_anki.py +++ b/python_pkg/anki_decks/polish_mountain_ranges/tests/test_polish_mountain_ranges_anki.py @@ -9,10 +9,12 @@ import geopandas as gpd import matplotlib.pyplot as plt import pytest from shapely.geometry import Polygon +from typing_extensions import Self import python_pkg.anki_decks.polish_mountain_ranges.polish_mountain_ranges_anki as _mod if TYPE_CHECKING: + from collections.abc import Callable, Iterable from pathlib import Path _init_worker = _mod._init_worker @@ -49,17 +51,26 @@ def _ranges() -> gpd.GeoDataFrame: class _FakePool: - def __init__(self, processes=None, initializer=None, initargs=()) -> None: + def __init__( + self, + _processes: int | None = None, + initializer: Callable[..., object] | None = None, + initargs: tuple[object, ...] = (), + ) -> None: if initializer: initializer(*initargs) - def imap_unordered(self, func, items): + def imap_unordered( + self, + func: Callable[[object], object], + items: Iterable[object], + ) -> list[object]: return [func(item) for item in items] - def __enter__(self): + def __enter__(self) -> Self: return self - def __exit__(self, *a): + def __exit__(self, *_args: object) -> None: pass diff --git a/python_pkg/anki_decks/polish_national_parks/tests/test_polish_national_parks_anki.py b/python_pkg/anki_decks/polish_national_parks/tests/test_polish_national_parks_anki.py index 2a3e962..e4a5490 100644 --- a/python_pkg/anki_decks/polish_national_parks/tests/test_polish_national_parks_anki.py +++ b/python_pkg/anki_decks/polish_national_parks/tests/test_polish_national_parks_anki.py @@ -3,6 +3,7 @@ from __future__ import annotations from pathlib import Path +from typing import TYPE_CHECKING from unittest.mock import patch import geopandas as gpd @@ -10,6 +11,11 @@ import matplotlib.pyplot as plt import pytest from shapely.geometry import Polygon +if TYPE_CHECKING: + from collections.abc import Callable, Iterable + + from typing_extensions import Self + try: from python_pkg.anki_decks.polish_national_parks.polish_national_parks_anki import ( _init_worker, @@ -73,17 +79,26 @@ def _small_park() -> gpd.GeoDataFrame: class _FakePool: - def __init__(self, processes=None, initializer=None, initargs=()) -> None: + def __init__( + self, + _processes: int | None = None, + initializer: Callable[..., object] | None = None, + initargs: tuple[object, ...] = (), + ) -> None: if initializer: initializer(*initargs) - def imap_unordered(self, func, items): + def imap_unordered( + self, + func: Callable[[object], object], + items: Iterable[object], + ) -> list[object]: return [func(item) for item in items] - def __enter__(self): + def __enter__(self) -> Self: return self - def __exit__(self, *a): + def __exit__(self, *_args: object) -> None: pass diff --git a/python_pkg/anki_decks/polish_nature_reserves/tests/test_polish_nature_reserves_anki.py b/python_pkg/anki_decks/polish_nature_reserves/tests/test_polish_nature_reserves_anki.py index 583fcca..32fd848 100644 --- a/python_pkg/anki_decks/polish_nature_reserves/tests/test_polish_nature_reserves_anki.py +++ b/python_pkg/anki_decks/polish_nature_reserves/tests/test_polish_nature_reserves_anki.py @@ -9,10 +9,12 @@ import geopandas as gpd import matplotlib.pyplot as plt import pytest from shapely.geometry import Polygon +from typing_extensions import Self import python_pkg.anki_decks.polish_nature_reserves.polish_nature_reserves_anki as _mod if TYPE_CHECKING: + from collections.abc import Callable, Iterable from pathlib import Path _init_worker = _mod._init_worker @@ -47,17 +49,26 @@ def _reserves() -> gpd.GeoDataFrame: class _FakePool: - def __init__(self, processes=None, initializer=None, initargs=()) -> None: + def __init__( + self, + _processes: int | None = None, + initializer: Callable[..., object] | None = None, + initargs: tuple[object, ...] = (), + ) -> None: if initializer: initializer(*initargs) - def imap_unordered(self, func, items): + def imap_unordered( + self, + func: Callable[[object], object], + items: Iterable[object], + ) -> list[object]: return [func(item) for item in items] - def __enter__(self): + def __enter__(self) -> Self: return self - def __exit__(self, *a): + def __exit__(self, *_args: object) -> None: pass diff --git a/python_pkg/anki_decks/polish_rivers/tests/test_polish_rivers_anki.py b/python_pkg/anki_decks/polish_rivers/tests/test_polish_rivers_anki.py index dffa0b8..7f2c862 100644 --- a/python_pkg/anki_decks/polish_rivers/tests/test_polish_rivers_anki.py +++ b/python_pkg/anki_decks/polish_rivers/tests/test_polish_rivers_anki.py @@ -3,7 +3,7 @@ from __future__ import annotations from pathlib import Path -from typing import Any +from typing import TYPE_CHECKING from unittest.mock import patch import geopandas as gpd @@ -12,6 +12,9 @@ import pytest from shapely.geometry import LineString, Polygon from typing_extensions import Self +if TYPE_CHECKING: + from collections.abc import Callable, Iterable + try: from python_pkg.anki_decks.polish_rivers.polish_rivers_anki import ( _init_worker, @@ -77,18 +80,18 @@ def _river_outside() -> gpd.GeoDataFrame: class _FakePool: def __init__( self, - processes: int | None = None, - initializer: Any = None, - initargs: tuple[Any, ...] = (), + _processes: int | None = None, + initializer: Callable[..., object] | None = None, + initargs: tuple[object, ...] = (), ) -> None: if initializer: initializer(*initargs) def imap_unordered( self, - func: Any, - items: Any, - ) -> list[Any]: + func: Callable[[object], object], + items: Iterable[object], + ) -> list[object]: return [func(item) for item in items] def __enter__(self) -> Self: diff --git a/python_pkg/anki_decks/polish_unesco_sites/tests/test_polish_unesco_sites_anki.py b/python_pkg/anki_decks/polish_unesco_sites/tests/test_polish_unesco_sites_anki.py index 9ff2e81..ac7da95 100644 --- a/python_pkg/anki_decks/polish_unesco_sites/tests/test_polish_unesco_sites_anki.py +++ b/python_pkg/anki_decks/polish_unesco_sites/tests/test_polish_unesco_sites_anki.py @@ -3,7 +3,7 @@ from __future__ import annotations from pathlib import Path -from typing import Any +from typing import TYPE_CHECKING from unittest.mock import patch import geopandas as gpd @@ -12,6 +12,9 @@ import pytest from shapely.geometry import Point, Polygon from typing_extensions import Self +if TYPE_CHECKING: + from collections.abc import Callable, Iterable + try: from python_pkg.anki_decks.polish_unesco_sites.polish_unesco_sites_anki import ( _init_worker, @@ -79,18 +82,18 @@ def _site_polygon() -> gpd.GeoDataFrame: class _FakePool: def __init__( self, - processes: int | None = None, - initializer: Any = None, - initargs: tuple[Any, ...] = (), + _processes: int | None = None, + initializer: Callable[..., object] | None = None, + initargs: tuple[object, ...] = (), ) -> None: if initializer: initializer(*initargs) def imap_unordered( self, - func: Any, - items: Any, - ) -> list[Any]: + func: Callable[[object], object], + items: Iterable[object], + ) -> list[object]: return [func(item) for item in items] def __enter__(self) -> Self: diff --git a/python_pkg/anki_decks/warsaw_streets/tests/test_warsaw_streets_anki.py b/python_pkg/anki_decks/warsaw_streets/tests/test_warsaw_streets_anki.py index 7504f3f..a17e11d 100644 --- a/python_pkg/anki_decks/warsaw_streets/tests/test_warsaw_streets_anki.py +++ b/python_pkg/anki_decks/warsaw_streets/tests/test_warsaw_streets_anki.py @@ -167,7 +167,7 @@ class TestLoadStreetData: patch.object(_mod_ref, "__file__", str(fake_file)), patch(f"{_MOD}.get_warsaw_streets", return_value=_street_segments_gdf()), ): - streets, boundary = load_street_data() + _, boundary = load_street_data() assert len(boundary) == 1 def test_file_not_found(self, tmp_path: Path) -> None: diff --git a/python_pkg/articles/tests/test_server_api.py b/python_pkg/articles/tests/test_server_api.py index aee342a..a9220d9 100644 --- a/python_pkg/articles/tests/test_server_api.py +++ b/python_pkg/articles/tests/test_server_api.py @@ -1,37 +1,78 @@ """Integration tests for the articles C server API.""" from http import HTTPStatus +import http.client import json import os from pathlib import Path +import shutil import socket import subprocess import time from typing import Any -import urllib.error -import urllib.request +import urllib.parse import pytest +class _HTTPError(Exception): + """HTTP error with status code.""" + + def __init__(self, code: int) -> None: + super().__init__(f"HTTP {code}") + self.code = code + + def _req( url: str, method: str = "GET", data: dict[str, Any] | bytes | None = None ) -> tuple[int, bytes]: """Send an HTTP request and return status code and body.""" if data is not None and not isinstance(data, bytes | bytearray): data = json.dumps(data).encode("utf-8") - req = urllib.request.Request(url, data=data, method=method) - req.add_header("Content-Type", "application/json") - with urllib.request.urlopen(req, timeout=5) as resp: + parsed = urllib.parse.urlparse(url) + conn = http.client.HTTPConnection(parsed.hostname, parsed.port, timeout=5) + try: + headers = {"Content-Type": "application/json"} + conn.request(method, parsed.path or "/", body=data, headers=headers) + resp = conn.getresponse() body = resp.read() - return resp.getcode(), body + status = resp.status + finally: + conn.close() + if status >= 400: + raise _HTTPError(status) + return status, body + + +def _probe_server(host: str, port: int) -> bool: + """Try a single GET to the server. Return True if it responded.""" + try: + conn = http.client.HTTPConnection(host, port, timeout=0.2) + try: + conn.request("GET", "/api/articles") + conn.getresponse().read() + return True + finally: + conn.close() + except OSError: + return False + + +def _wait_for_server(host: str, port: int, attempts: int = 30) -> None: + """Poll the server until it responds or attempts are exhausted.""" + for _ in range(attempts): + if _probe_server(host, port): + return + time.sleep(0.05) def test_crud_roundtrip(tmp_path: Path) -> None: """Test full CRUD lifecycle for articles API.""" # Build C server here = Path(__file__).resolve().parent.parent - subprocess.run(["make", "-s", "server_c"], check=True, cwd=str(here)) + make_path = shutil.which("make") + assert make_path is not None, "make not found in PATH" + subprocess.run([make_path, "-s", "server_c"], check=True, cwd=str(here)) # Find a free port with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: @@ -47,16 +88,7 @@ def test_crud_roundtrip(tmp_path: Path) -> None: env["PORT"] = str(port) srv = subprocess.Popen(["./server_c"], cwd=str(here), env=env) try: - # wait briefly for server to be ready - for _ in range(30): - try: - with urllib.request.urlopen( - base + "/api/articles", timeout=0.2 - ) as resp: - resp.read() - break - except (OSError, urllib.error.URLError): - time.sleep(0.05) + _wait_for_server(host, port) # Create code, body = _req( @@ -97,10 +129,9 @@ def test_crud_roundtrip(tmp_path: Path) -> None: assert code == HTTPStatus.NO_CONTENT # Ensure gone - with pytest.raises(urllib.error.HTTPError) as exc_info: + with pytest.raises(_HTTPError) as exc_info: _req(base + f"/api/articles/{art_id}") assert exc_info.value.code == HTTPStatus.NOT_FOUND - exc_info.value.close() finally: srv.terminate() diff --git a/python_pkg/brightness_controller/tests/test_auto_brightness_daemon.py b/python_pkg/brightness_controller/tests/test_auto_brightness_daemon.py index a10fd78..34da520 100644 --- a/python_pkg/brightness_controller/tests/test_auto_brightness_daemon.py +++ b/python_pkg/brightness_controller/tests/test_auto_brightness_daemon.py @@ -20,12 +20,12 @@ class TestFindAlsDevice: "glob", return_value=[Path("/sys/bus/iio/devices/iio0/in_illuminance_raw")], ) - def test_found(self, _mock_glob: MagicMock) -> None: + def test_found(self, mock_glob: MagicMock) -> None: result = auto_brightness_daemon._find_als_device() assert result == Path("/sys/bus/iio/devices/iio0") @patch.object(Path, "glob", return_value=[]) - def test_not_found(self, _mock_glob: MagicMock) -> None: + def test_not_found(self, mock_glob: MagicMock) -> None: assert auto_brightness_daemon._find_als_device() is None diff --git a/python_pkg/brightness_controller/tests/test_auto_brightness_daemon_part2.py b/python_pkg/brightness_controller/tests/test_auto_brightness_daemon_part2.py index 78f2656..e528cf0 100644 --- a/python_pkg/brightness_controller/tests/test_auto_brightness_daemon_part2.py +++ b/python_pkg/brightness_controller/tests/test_auto_brightness_daemon_part2.py @@ -17,7 +17,7 @@ class TestMainNoAls: """Tests for main() when no ALS device is found.""" @patch(f"{MOD}._find_als_device", return_value=None) - def test_exits_when_no_als(self, _mock_find: MagicMock) -> None: + def test_exits_when_no_als(self, mock_find: MagicMock) -> None: with pytest.raises(SystemExit, match="1"): auto_brightness_daemon.main() @@ -62,10 +62,10 @@ class TestMainDaemonLoop: patch(f"{MOD}._lux_to_brightness", return_value=75), patch(f"{MOD}._get_brightness", return_value=current_brightness), patch(f"{MOD}._set_brightness", mock_set_brightness), + contextlib.suppress(KeyboardInterrupt), ): # Simulate SIGINT by raising KeyboardInterrupt in sleep - with contextlib.suppress(KeyboardInterrupt): - auto_brightness_daemon.main() + auto_brightness_daemon.main() return mock_set_brightness, mock_lux @@ -96,9 +96,9 @@ class TestMainDaemonLoop: patch(f"{MOD}._lux_to_brightness", return_value=74), patch(f"{MOD}._get_brightness", return_value=74), patch(f"{MOD}._set_brightness") as mock_set, + contextlib.suppress(KeyboardInterrupt), ): - with contextlib.suppress(KeyboardInterrupt): - auto_brightness_daemon.main() + auto_brightness_daemon.main() mock_set.assert_not_called() def test_skips_when_brightness_negative(self) -> None: @@ -116,9 +116,9 @@ class TestMainDaemonLoop: patch(f"{MOD}._lux_to_brightness", return_value=75), patch(f"{MOD}._get_brightness", return_value=-1), patch(f"{MOD}._set_brightness") as mock_set, + contextlib.suppress(KeyboardInterrupt), ): - with contextlib.suppress(KeyboardInterrupt): - auto_brightness_daemon.main() + auto_brightness_daemon.main() mock_set.assert_not_called() def test_creates_control_file_when_missing(self) -> None: @@ -133,9 +133,9 @@ class TestMainDaemonLoop: patch(f"{MOD}.signal.signal"), patch(f"{MOD}.time.sleep", side_effect=KeyboardInterrupt), patch(f"{MOD}._is_enabled", return_value=False), + contextlib.suppress(KeyboardInterrupt), ): - with contextlib.suppress(KeyboardInterrupt): - auto_brightness_daemon.main() + auto_brightness_daemon.main() mock_set_enabled.assert_called_once_with(enabled=True) def test_does_not_create_file_when_exists(self) -> None: @@ -150,9 +150,9 @@ class TestMainDaemonLoop: patch(f"{MOD}.signal.signal"), patch(f"{MOD}.time.sleep", side_effect=KeyboardInterrupt), patch(f"{MOD}._is_enabled", return_value=False), + contextlib.suppress(KeyboardInterrupt), ): - with contextlib.suppress(KeyboardInterrupt): - auto_brightness_daemon.main() + auto_brightness_daemon.main() mock_set_enabled.assert_not_called() def test_handles_exception_in_loop_gracefully(self) -> None: @@ -166,9 +166,9 @@ class TestMainDaemonLoop: patch(f"{MOD}.signal.signal"), patch(f"{MOD}.time.sleep", side_effect=[None, KeyboardInterrupt]), patch(f"{MOD}._is_enabled", side_effect=OSError("disk fail")), + contextlib.suppress(KeyboardInterrupt), ): - with contextlib.suppress(KeyboardInterrupt): - auto_brightness_daemon.main() + auto_brightness_daemon.main() # No crash = exception was handled def test_signal_handler_stops_loop(self) -> None: @@ -189,9 +189,9 @@ class TestMainDaemonLoop: patch(f"{MOD}.signal.signal", side_effect=capture_signal), patch(f"{MOD}.time.sleep", side_effect=KeyboardInterrupt), patch(f"{MOD}._is_enabled", return_value=False), + contextlib.suppress(KeyboardInterrupt), ): - with contextlib.suppress(KeyboardInterrupt): - auto_brightness_daemon.main() + auto_brightness_daemon.main() # Verify we captured a SIGTERM handler assert signal.SIGTERM in captured_handler @@ -217,9 +217,9 @@ class TestMainDaemonLoop: patch(f"{MOD}._lux_to_brightness", return_value=10), patch(f"{MOD}._get_brightness", return_value=90), patch(f"{MOD}._set_brightness") as mock_set, + contextlib.suppress(KeyboardInterrupt), ): - with contextlib.suppress(KeyboardInterrupt): - auto_brightness_daemon.main() + auto_brightness_daemon.main() # delta=-80, step=-5, new_val=85 mock_set.assert_called_with(85) diff --git a/python_pkg/brightness_controller/tests/test_brightness_controller.py b/python_pkg/brightness_controller/tests/test_brightness_controller.py index 274761f..445467e 100644 --- a/python_pkg/brightness_controller/tests/test_brightness_controller.py +++ b/python_pkg/brightness_controller/tests/test_brightness_controller.py @@ -20,12 +20,12 @@ class TestFindAlsDevice: "glob", return_value=[Path("/sys/bus/iio/devices/iio0/in_illuminance_raw")], ) - def test_found(self, _mock_glob: MagicMock) -> None: + def test_found(self, mock_glob: MagicMock) -> None: result = brightness_controller._find_als_device() assert result == Path("/sys/bus/iio/devices/iio0") @patch.object(Path, "glob", return_value=[]) - def test_not_found(self, _mock_glob: MagicMock) -> None: + def test_not_found(self, mock_glob: MagicMock) -> None: assert brightness_controller._find_als_device() is None @@ -327,7 +327,7 @@ class TestRefreshBrightness: "python_pkg.brightness_controller.brightness_controller._get_brightness", return_value=75, ) - def test_updates_ui(self, _mock_get: MagicMock) -> None: + def test_updates_ui(self, mock_get: MagicMock) -> None: ctrl = _make_controller() ctrl.pct_var = MagicMock() ctrl.slider_var = MagicMock() @@ -339,7 +339,7 @@ class TestRefreshBrightness: "python_pkg.brightness_controller.brightness_controller._get_brightness", return_value=-1, ) - def test_error(self, _mock_get: MagicMock) -> None: + def test_error(self, mock_get: MagicMock) -> None: ctrl = _make_controller() ctrl.pct_var = MagicMock() ctrl._refresh_brightness() @@ -378,7 +378,7 @@ class TestOnSliderMove: ctrl.pct_var.set.assert_not_called() @patch("python_pkg.brightness_controller.brightness_controller._set_brightness") - def test_disables_auto_mode(self, _mock_set: MagicMock) -> None: + def test_disables_auto_mode(self, mock_set: MagicMock) -> None: ctrl = _make_controller(daemon_state=True) ctrl.auto_mode = True ctrl.pct_var = MagicMock() @@ -396,7 +396,7 @@ class TestSetPct: "python_pkg.brightness_controller.brightness_controller._get_brightness", return_value=25, ) - def test_sets_brightness(self, _mock_get: MagicMock, mock_set: MagicMock) -> None: + def test_sets_brightness(self, mock_get: MagicMock, mock_set: MagicMock) -> None: ctrl = _make_controller() ctrl.pct_var = MagicMock() ctrl.slider_var = MagicMock() @@ -417,7 +417,7 @@ class TestDecrease: "python_pkg.brightness_controller.brightness_controller._get_brightness", return_value=50, ) - def test_decrease(self, _mock_get: MagicMock, mock_set: MagicMock) -> None: + def test_decrease(self, mock_get: MagicMock, mock_set: MagicMock) -> None: ctrl = _make_controller() ctrl.pct_var = MagicMock() ctrl.slider_var = MagicMock() @@ -429,7 +429,7 @@ class TestDecrease: "python_pkg.brightness_controller.brightness_controller._get_brightness", return_value=2, ) - def test_clamps_to_zero(self, _mock_get: MagicMock, mock_set: MagicMock) -> None: + def test_clamps_to_zero(self, mock_get: MagicMock, mock_set: MagicMock) -> None: ctrl = _make_controller() ctrl.pct_var = MagicMock() ctrl.slider_var = MagicMock() @@ -449,7 +449,7 @@ class TestIncrease: "python_pkg.brightness_controller.brightness_controller._get_brightness", return_value=50, ) - def test_increase(self, _mock_get: MagicMock, mock_set: MagicMock) -> None: + def test_increase(self, mock_get: MagicMock, mock_set: MagicMock) -> None: ctrl = _make_controller() ctrl.pct_var = MagicMock() ctrl.slider_var = MagicMock() @@ -461,7 +461,7 @@ class TestIncrease: "python_pkg.brightness_controller.brightness_controller._get_brightness", return_value=98, ) - def test_clamps_to_100(self, _mock_get: MagicMock, mock_set: MagicMock) -> None: + def test_clamps_to_100(self, mock_get: MagicMock, mock_set: MagicMock) -> None: ctrl = _make_controller() ctrl.pct_var = MagicMock() ctrl.slider_var = MagicMock() diff --git a/python_pkg/brightness_controller/tests/test_brightness_controller_part2.py b/python_pkg/brightness_controller/tests/test_brightness_controller_part2.py index 3c67bc2..8e72c54 100644 --- a/python_pkg/brightness_controller/tests/test_brightness_controller_part2.py +++ b/python_pkg/brightness_controller/tests/test_brightness_controller_part2.py @@ -91,7 +91,7 @@ class TestPollAls: """Tests for _poll_als.""" @patch(f"{MOD}._read_lux", return_value=42.5) - def test_updates_lux_display(self, _mock_lux: MagicMock) -> None: + def test_updates_lux_display(self, mock_lux: MagicMock) -> None: ctrl = _make_controller(als_path=Path("/fake")) ctrl.lux_var = MagicMock() ctrl.root = MagicMock() @@ -105,7 +105,7 @@ class TestPollAls: ctrl.root.after.assert_called_once() @patch(f"{MOD}._read_lux", side_effect=OSError("sensor fail")) - def test_sensor_error(self, _mock_lux: MagicMock) -> None: + def test_sensor_error(self, mock_lux: MagicMock) -> None: ctrl = _make_controller(als_path=Path("/fake")) ctrl.lux_var = MagicMock() ctrl.root = MagicMock() @@ -118,7 +118,7 @@ class TestPollAls: ctrl.lux_var.set.assert_called_with("sensor error") @patch(f"{MOD}._read_lux", side_effect=ValueError("bad value")) - def test_sensor_value_error(self, _mock_lux: MagicMock) -> None: + def test_sensor_value_error(self, mock_lux: MagicMock) -> None: ctrl = _make_controller(als_path=Path("/fake")) ctrl.lux_var = MagicMock() ctrl.root = MagicMock() @@ -131,7 +131,7 @@ class TestPollAls: ctrl.lux_var.set.assert_called_with("sensor error") @patch(f"{MOD}._read_lux", return_value=10.0) - def test_syncs_daemon_state_change(self, _mock_lux: MagicMock) -> None: + def test_syncs_daemon_state_change(self, mock_lux: MagicMock) -> None: """When daemon state differs from auto_mode, syncs it.""" ctrl = _make_controller(als_path=Path("/fake")) ctrl.auto_mode = False @@ -148,7 +148,7 @@ class TestPollAls: assert ctrl.auto_mode is True @patch(f"{MOD}._read_lux", return_value=10.0) - def test_no_sync_when_same(self, _mock_lux: MagicMock) -> None: + def test_no_sync_when_same(self, mock_lux: MagicMock) -> None: """When daemon state matches auto_mode, no sync needed.""" ctrl = _make_controller(als_path=Path("/fake")) ctrl.auto_mode = False @@ -177,7 +177,7 @@ class TestPollBrightness: """Tests for _poll_brightness.""" @patch(f"{MOD}._get_brightness", return_value=60) - def test_refreshes_when_not_auto(self, _mock_get: MagicMock) -> None: + def test_refreshes_when_not_auto(self, mock_get: MagicMock) -> None: ctrl = _make_controller() ctrl.auto_mode = False ctrl.pct_var = MagicMock() diff --git a/python_pkg/brother_printer/cups_service.py b/python_pkg/brother_printer/cups_service.py index fc10918..00da3f6 100644 --- a/python_pkg/brother_printer/cups_service.py +++ b/python_pkg/brother_printer/cups_service.py @@ -2,6 +2,7 @@ from __future__ import annotations +import importlib import json import logging from pathlib import Path @@ -9,6 +10,7 @@ import re import shutil import subprocess import time +from typing import TYPE_CHECKING import urllib.parse from python_pkg.brother_printer.constants import ( @@ -30,21 +32,33 @@ from python_pkg.brother_printer.data_classes import ( USBResult, ) +if TYPE_CHECKING: + import types + logger = logging.getLogger(__name__) CUPS_PAGE_LOG = Path(CUPS_PAGE_LOG_PATH) CONSUMABLE_STATE_FILE = Path.home() / CONSUMABLE_STATE_DIR / "state.json" +def _import_or_raise(name: str) -> types.ModuleType: + """Import a module or raise ImportError with a helpful message.""" + try: + return importlib.import_module(name) + except ImportError as e: + msg = f"{name} is required but not installed" + raise ImportError(msg) from e + + # ── pyusb device info ──────────────────────────────────────────────── def _get_pyusb_device_info() -> dict[str, str]: """Get Brother USB printer info via pyusb (no interface claim needed).""" try: - import usb.core + usb_core = _import_or_raise("usb.core") - dev = usb.core.find(idVendor=BROTHER_USB_VENDOR_ID) + dev = usb_core.find(idVendor=BROTHER_USB_VENDOR_ID) if dev is None: return {} except (ImportError, OSError, ValueError): @@ -133,12 +147,12 @@ def _query_usb_port_status_raw() -> USBPortStatus | None: Returns None if the query fails. """ try: - import usb.core - import usb.util + usb_core = _import_or_raise("usb.core") + usb_util = _import_or_raise("usb.util") except ImportError: return None - dev = usb.core.find(idVendor=BROTHER_USB_VENDOR_ID) + dev = usb_core.find(idVendor=BROTHER_USB_VENDOR_ID) if dev is None: return None @@ -148,17 +162,17 @@ def _query_usb_port_status_raw() -> USBPortStatus | None: try: dev.reset() time.sleep(2) - dev = usb.core.find(idVendor=BROTHER_USB_VENDOR_ID) + dev = usb_core.find(idVendor=BROTHER_USB_VENDOR_ID) if dev is None: return None try: if dev.is_kernel_driver_active(0): dev.detach_kernel_driver(0) - except (usb.core.USBError, NotImplementedError): + except (usb_core.USBError, NotImplementedError): pass - usb.util.claim_interface(dev, 0) + usb_util.claim_interface(dev, 0) try: # USB Printer Class GET_PORT_STATUS (bRequest=0x01) raw = dev.ctrl_transfer(0xA1, 0x01, 0, 0, 1, timeout=5000) @@ -170,8 +184,8 @@ def _query_usb_port_status_raw() -> USBPortStatus | None: raw_byte=port_byte, ) finally: - usb.util.release_interface(dev, 0) - usb.util.dispose_resources(dev) + usb_util.release_interface(dev, 0) + usb_util.dispose_resources(dev) except (OSError, ValueError): logger.debug("USB port status query failed", exc_info=True) return None diff --git a/python_pkg/brother_printer/tests/test_check_brother_printer.py b/python_pkg/brother_printer/tests/test_check_brother_printer.py index d3f0f88..dc64aa7 100644 --- a/python_pkg/brother_printer/tests/test_check_brother_printer.py +++ b/python_pkg/brother_printer/tests/test_check_brother_printer.py @@ -15,18 +15,19 @@ from python_pkg.brother_printer.check_brother_printer import ( _run_usb_mode, main, ) +from python_pkg.brother_printer.data_classes import USBResult MOD = "python_pkg.brother_printer.check_brother_printer" class TestDiscoverNetworkPrinter: @patch(f"{MOD}.shutil.which", return_value=None) - def test_no_lpstat(self, _m: MagicMock) -> None: + def test_no_lpstat(self, m: MagicMock) -> None: assert _discover_network_printer() == "" @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpstat") - def test_found_ip(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_found_ip(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.return_value = MagicMock( stdout="device for BrotherHL1110: ipp://192.168.1.100/ipp\n", ) @@ -34,7 +35,7 @@ class TestDiscoverNetworkPrinter: @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpstat") - def test_socket(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_socket(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.return_value = MagicMock( stdout="device for BrotherHL1110: socket://10.0.0.5:9100\n", ) @@ -42,7 +43,7 @@ class TestDiscoverNetworkPrinter: @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpstat") - def test_no_match(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_no_match(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.return_value = MagicMock( stdout="device for BrotherHL1110: usb://Brother/HL-1110\n", ) @@ -50,30 +51,32 @@ class TestDiscoverNetworkPrinter: @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpstat") - def test_timeout(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_timeout(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.side_effect = subprocess.TimeoutExpired("lpstat", 5) assert _discover_network_printer() == "" @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpstat") - def test_oserror(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_oserror(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.side_effect = OSError("fail") assert _discover_network_printer() == "" class TestRunNetworkMode: @patch(f"{MOD}.shutil.which", return_value=None) - def test_no_snmpwalk(self, _m: MagicMock) -> None: - with patch("sys.stdout", new_callable=StringIO): - with pytest.raises(SystemExit): - _run_network_mode("1.2.3.4") + def test_no_snmpwalk(self, m: MagicMock) -> None: + with ( + patch("sys.stdout", new_callable=StringIO), + pytest.raises(SystemExit), + ): + _run_network_mode("1.2.3.4") @patch(f"{MOD}.display_network_results") @patch(f"{MOD}.query_network_snmp") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/snmpwalk") def test_success( self, - _w: MagicMock, + w: MagicMock, mock_query: MagicMock, mock_display: MagicMock, ) -> None: @@ -101,9 +104,11 @@ class TestRunUsbMode: class TestNoPrinterFound: def test_exits(self) -> None: - with patch("sys.stdout", new_callable=StringIO): - with pytest.raises(SystemExit): - _no_printer_found() + with ( + patch("sys.stdout", new_callable=StringIO), + pytest.raises(SystemExit), + ): + _no_printer_found() class TestMain: @@ -118,14 +123,16 @@ class TestMain: mock_reset.assert_called_once_with("drum") @patch(f"{MOD}.os.geteuid", return_value=1000) - def test_not_root(self, _m: MagicMock) -> None: - with patch("sys.stdout", new_callable=StringIO): - with pytest.raises(SystemExit): - main([]) + def test_not_root(self, m: MagicMock) -> None: + with ( + patch("sys.stdout", new_callable=StringIO), + pytest.raises(SystemExit), + ): + main([]) @patch(f"{MOD}._run_network_mode") @patch(f"{MOD}.os.geteuid", return_value=0) - def test_with_ip(self, _g: MagicMock, mock_net: MagicMock) -> None: + def test_with_ip(self, g: MagicMock, mock_net: MagicMock) -> None: main(["1.2.3.4"]) mock_net.assert_called_once_with("1.2.3.4") @@ -134,34 +141,28 @@ class TestMain: @patch(f"{MOD}.os.geteuid", return_value=0) def test_usb_found( self, - _g: MagicMock, - _f: MagicMock, + g: MagicMock, + f: MagicMock, mock_usb: MagicMock, ) -> None: main([]) mock_usb.assert_called_once() - @patch(f"{MOD}.display_network_results") - @patch(f"{MOD}.query_network_snmp") - @patch(f"{MOD}.shutil.which", return_value="/usr/bin/snmpwalk") - @patch(f"{MOD}._discover_network_printer", return_value="192.168.1.100") - @patch(f"{MOD}.find_brother_usb", return_value="") - @patch(f"{MOD}.os.geteuid", return_value=0) - def test_network_discovered( - self, - _g: MagicMock, - _f: MagicMock, - _d: MagicMock, - _w: MagicMock, - mock_query: MagicMock, - mock_display: MagicMock, - ) -> None: + def test_network_discovered(self) -> None: from python_pkg.brother_printer.data_classes import NetworkResult - mock_query.return_value = NetworkResult(ip="192.168.1.100") - with patch("sys.stdout", new_callable=StringIO): + with ( + patch(f"{MOD}.os.geteuid", return_value=0), + patch(f"{MOD}.find_brother_usb", return_value=""), + patch(f"{MOD}._discover_network_printer", return_value="192.168.1.100"), + patch(f"{MOD}.shutil.which", return_value="/usr/bin/snmpwalk"), + patch(f"{MOD}.query_network_snmp") as mock_query, + patch(f"{MOD}.display_network_results") as mock_display, + patch("sys.stdout", new_callable=StringIO), + ): + mock_query.return_value = NetworkResult(ip="192.168.1.100") main([]) - mock_display.assert_called_once() + mock_display.assert_called_once() @patch(f"{MOD}._no_printer_found") @patch(f"{MOD}._discover_network_printer", return_value="") @@ -169,9 +170,9 @@ class TestMain: @patch(f"{MOD}.os.geteuid", return_value=0) def test_nothing_found( self, - _g: MagicMock, - _f: MagicMock, - _d: MagicMock, + g: MagicMock, + f: MagicMock, + d: MagicMock, mock_no: MagicMock, ) -> None: main([]) @@ -184,10 +185,10 @@ class TestMain: @patch(f"{MOD}.os.geteuid", return_value=0) def test_network_discovered_no_snmpwalk( self, - _g: MagicMock, - _f: MagicMock, - _d: MagicMock, - _w: MagicMock, + g: MagicMock, + f: MagicMock, + d: MagicMock, + w: MagicMock, mock_no: MagicMock, ) -> None: main([]) @@ -202,10 +203,9 @@ class TestMain: mock_reset.assert_called_once_with("toner") @patch(f"{MOD}.os.geteuid", return_value=1000) - def test_not_root_with_args(self, _g: MagicMock) -> None: - with patch("sys.stdout", new_callable=StringIO): - with pytest.raises(SystemExit): - main(["1.2.3.4"]) - - -from python_pkg.brother_printer.data_classes import USBResult + def test_not_root_with_args(self, g: MagicMock) -> None: + with ( + patch("sys.stdout", new_callable=StringIO), + pytest.raises(SystemExit), + ): + main(["1.2.3.4"]) diff --git a/python_pkg/brother_printer/tests/test_constants.py b/python_pkg/brother_printer/tests/test_constants.py index 9fb6cd1..f997e2b 100644 --- a/python_pkg/brother_printer/tests/test_constants.py +++ b/python_pkg/brother_printer/tests/test_constants.py @@ -96,7 +96,7 @@ class TestGetStatusInfo: assert action == "" def test_toner_low(self) -> None: - severity, text, action = get_status_info("30010") + severity, text, _ = get_status_info("30010") assert severity == "warn" assert "Toner Low" in text @@ -107,7 +107,7 @@ class TestGetStatusInfo: assert action != "" def test_invalid_code(self) -> None: - severity, text, action = get_status_info("not_a_number") + severity, text, _ = get_status_info("not_a_number") assert severity == "info" assert "Unknown" in text diff --git a/python_pkg/brother_printer/tests/test_cups_queue.py b/python_pkg/brother_printer/tests/test_cups_queue.py index 01d4e73..ba975d8 100644 --- a/python_pkg/brother_printer/tests/test_cups_queue.py +++ b/python_pkg/brother_printer/tests/test_cups_queue.py @@ -65,14 +65,14 @@ class TestParseLpstatJobs: class TestGetCupsQueueStatus: @patch(f"{MOD}.find_cups_printer_name", return_value="") - def test_no_printer(self, _f: MagicMock) -> None: + def test_no_printer(self, f: MagicMock) -> None: result = get_cups_queue_status() assert result.printer_name == "" @patch(f"{MOD}._check_cups_backend_errors", return_value=(False, "")) @patch(f"{MOD}.shutil.which", return_value=None) @patch(f"{MOD}.find_cups_printer_name", return_value="BrotherHL1110") - def test_no_lpstat(self, _f: MagicMock, _w: MagicMock, _c: MagicMock) -> None: + def test_no_lpstat(self, f: MagicMock, w: MagicMock, c: MagicMock) -> None: result = get_cups_queue_status() assert result.printer_name == "BrotherHL1110" @@ -82,10 +82,10 @@ class TestGetCupsQueueStatus: @patch(f"{MOD}.find_cups_printer_name", return_value="BrotherHL1110") def test_full_status( self, - _f: MagicMock, - _w: MagicMock, + f: MagicMock, + w: MagicMock, mock_run: MagicMock, - _c: MagicMock, + c: MagicMock, ) -> None: # First call for printer status, second for jobs mock_run.side_effect = [ @@ -108,10 +108,10 @@ class TestGetCupsQueueStatus: @patch(f"{MOD}.find_cups_printer_name", return_value="BrotherHL1110") def test_with_backend_errors( self, - _f: MagicMock, - _w: MagicMock, + f: MagicMock, + w: MagicMock, mock_run: MagicMock, - _c: MagicMock, + c: MagicMock, ) -> None: mock_run.side_effect = [ MagicMock(stdout="printer BrotherHL1110 disabled\n"), @@ -126,10 +126,10 @@ class TestGetCupsQueueStatus: @patch(f"{MOD}.find_cups_printer_name", return_value="BrotherHL1110") def test_printer_status_timeout( self, - _f: MagicMock, - _w: MagicMock, + f: MagicMock, + w: MagicMock, mock_run: MagicMock, - _c: MagicMock, + c: MagicMock, ) -> None: mock_run.side_effect = [ subprocess.TimeoutExpired("lpstat", 5), @@ -144,10 +144,10 @@ class TestGetCupsQueueStatus: @patch(f"{MOD}.find_cups_printer_name", return_value="BrotherHL1110") def test_job_status_timeout( self, - _f: MagicMock, - _w: MagicMock, + f: MagicMock, + w: MagicMock, mock_run: MagicMock, - _c: MagicMock, + c: MagicMock, ) -> None: mock_run.side_effect = [ MagicMock(stdout=""), @@ -162,10 +162,10 @@ class TestGetCupsQueueStatus: @patch(f"{MOD}.find_cups_printer_name", return_value="BrotherHL1110") def test_no_matching_printer_line( self, - _f: MagicMock, - _w: MagicMock, + f: MagicMock, + w: MagicMock, mock_run: MagicMock, - _c: MagicMock, + c: MagicMock, ) -> None: mock_run.side_effect = [ MagicMock(stdout="printer HP is idle.\n"), @@ -177,26 +177,26 @@ class TestGetCupsQueueStatus: class TestCupsEnablePrinter: @patch(f"{MOD}.shutil.which", return_value=None) - def test_no_cupsenable(self, _m: MagicMock) -> None: + def test_no_cupsenable(self, m: MagicMock) -> None: with patch("sys.stdout", new_callable=StringIO): assert _cups_enable_printer("B") is False @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/cupsenable") - def test_success(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_success(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.return_value = MagicMock() assert _cups_enable_printer("B") is True @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/cupsenable") - def test_timeout(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_timeout(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.side_effect = subprocess.TimeoutExpired("cupsenable", 5) with patch("sys.stdout", new_callable=StringIO): assert _cups_enable_printer("B") is False @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/cupsenable") - def test_oserror(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_oserror(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.side_effect = OSError("fail") with patch("sys.stdout", new_callable=StringIO): assert _cups_enable_printer("B") is False @@ -204,19 +204,19 @@ class TestCupsEnablePrinter: class TestCupsCancelAllJobs: @patch(f"{MOD}.shutil.which", return_value=None) - def test_no_cancel(self, _m: MagicMock) -> None: + def test_no_cancel(self, m: MagicMock) -> None: with patch("sys.stdout", new_callable=StringIO): assert _cups_cancel_all_jobs("B") is False @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/cancel") - def test_success(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_success(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.return_value = MagicMock() assert _cups_cancel_all_jobs("B") is True @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/cancel") - def test_error(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_error(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.side_effect = subprocess.CalledProcessError(1, "cancel") with patch("sys.stdout", new_callable=StringIO): assert _cups_cancel_all_jobs("B") is False @@ -224,25 +224,25 @@ class TestCupsCancelAllJobs: class TestCupsCancelJob: @patch(f"{MOD}.shutil.which", return_value=None) - def test_no_cancel(self, _m: MagicMock) -> None: + def test_no_cancel(self, m: MagicMock) -> None: assert _cups_cancel_job("job-1") is False @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/cancel") - def test_success(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_success(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.return_value = MagicMock() assert _cups_cancel_job("job-1") is True @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/cancel") - def test_error(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_error(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.side_effect = subprocess.CalledProcessError(1, "cancel") assert _cups_cancel_job("job-1") is False class TestCupsRestartService: @patch(f"{MOD}.shutil.which", return_value=None) - def test_no_systemctl(self, _m: MagicMock) -> None: + def test_no_systemctl(self, m: MagicMock) -> None: with patch("sys.stdout", new_callable=StringIO): assert _cups_restart_service() is False @@ -252,10 +252,10 @@ class TestCupsRestartService: @patch(f"{MOD}.shutil.which", return_value="/usr/bin/systemctl") def test_success( self, - _w: MagicMock, + w: MagicMock, mock_popen: MagicMock, mock_time: MagicMock, - _s: MagicMock, + s: MagicMock, ) -> None: proc = MagicMock() proc.poll.side_effect = [None, 0] @@ -271,10 +271,10 @@ class TestCupsRestartService: @patch(f"{MOD}.shutil.which", return_value="/usr/bin/systemctl") def test_timeout( self, - _w: MagicMock, + w: MagicMock, mock_popen: MagicMock, mock_time: MagicMock, - _s: MagicMock, + s: MagicMock, ) -> None: proc = MagicMock() proc.poll.return_value = None @@ -290,10 +290,10 @@ class TestCupsRestartService: @patch(f"{MOD}.shutil.which", return_value="/usr/bin/systemctl") def test_nonzero_exit( self, - _w: MagicMock, + w: MagicMock, mock_popen: MagicMock, mock_time: MagicMock, - _s: MagicMock, + s: MagicMock, ) -> None: proc = MagicMock() proc.poll.side_effect = [None, 1] @@ -305,7 +305,7 @@ class TestCupsRestartService: @patch(f"{MOD}.subprocess.Popen") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/systemctl") - def test_oserror(self, _w: MagicMock, mock_popen: MagicMock) -> None: + def test_oserror(self, w: MagicMock, mock_popen: MagicMock) -> None: mock_popen.side_effect = OSError("fail") with patch("sys.stdout", new_callable=StringIO): assert _cups_restart_service() is False @@ -313,12 +313,12 @@ class TestCupsRestartService: class TestIsCupsPrinterHealthy: @patch(f"{MOD}.shutil.which", return_value=None) - def test_no_lpstat(self, _m: MagicMock) -> None: + def test_no_lpstat(self, m: MagicMock) -> None: assert _is_cups_printer_healthy("B") is False @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpstat") - def test_healthy(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_healthy(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.return_value = MagicMock( stdout="printer BrotherHL1110 is idle. enabled since Mon\n", ) @@ -326,7 +326,7 @@ class TestIsCupsPrinterHealthy: @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpstat") - def test_not_healthy(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_not_healthy(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.return_value = MagicMock( stdout="printer BrotherHL1110 disabled\n", ) @@ -334,7 +334,7 @@ class TestIsCupsPrinterHealthy: @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpstat") - def test_timeout(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_timeout(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.side_effect = subprocess.TimeoutExpired("lpstat", 5) assert _is_cups_printer_healthy("B") is False @@ -342,7 +342,7 @@ class TestIsCupsPrinterHealthy: class TestFindBackendErrorInLog: def test_no_errors(self) -> None: lines = ["[2025-01-01] Completed job\n"] - err, ts, success_ts = _find_backend_error_in_log(lines) + err, _, _ = _find_backend_error_in_log(lines) assert err == "" def test_backend_error(self) -> None: @@ -359,13 +359,13 @@ class TestFindBackendErrorInLog: lines = [ "[2025-01-02] stopped with status 1", ] - err, ts, success_ts = _find_backend_error_in_log(lines) + err, ts, _ = _find_backend_error_in_log(lines) assert "stopped with status" in err assert ts == "2025-01-02" def test_error_no_timestamp(self) -> None: lines = ["backend errors no timestamp here"] - err, ts, success_ts = _find_backend_error_in_log(lines) + err, ts, _ = _find_backend_error_in_log(lines) assert "backend errors" in err assert ts == "" @@ -374,14 +374,14 @@ class TestFindBackendErrorInLog: "[2025-01-01] page total 10", "[2025-01-02] backend errors", ] - err, ts, success_ts = _find_backend_error_in_log(lines) + _, _, success_ts = _find_backend_error_in_log(lines) assert success_ts == "2025-01-01" def test_no_success_after_error(self) -> None: lines = [ "[2025-01-02] backend errors", ] - err, ts, success_ts = _find_backend_error_in_log(lines) + _, _, success_ts = _find_backend_error_in_log(lines) assert success_ts == "" def test_completed_no_timestamp(self) -> None: @@ -389,37 +389,37 @@ class TestFindBackendErrorInLog: "Completed job", "[2025-01-02] backend errors", ] - err, ts, success_ts = _find_backend_error_in_log(lines) + _, _, success_ts = _find_backend_error_in_log(lines) assert success_ts == "" class TestCheckCupsBackendErrors: @patch(f"{MOD}._is_cups_printer_healthy", return_value=True) - def test_healthy_printer(self, _m: MagicMock) -> None: - has_errors, msg = _check_cups_backend_errors("B") + def test_healthy_printer(self, m: MagicMock) -> None: + has_errors, _ = _check_cups_backend_errors("B") assert has_errors is False @patch(f"{MOD}._find_backend_error_in_log", return_value=("", "", "")) @patch(f"{MOD}._is_cups_printer_healthy", return_value=False) - def test_no_log_file(self, _h: MagicMock, _f: MagicMock) -> None: + def test_no_log_file(self, h: MagicMock, f: MagicMock) -> None: with patch(f"{MOD}.Path") as mock_path: mock_log = MagicMock() mock_log.exists.return_value = False mock_path.return_value = mock_log - has_errors, msg = _check_cups_backend_errors("B") + has_errors, _ = _check_cups_backend_errors("B") assert has_errors is False @patch( f"{MOD}._find_backend_error_in_log", return_value=("error", "2025-01-02", "") ) @patch(f"{MOD}._is_cups_printer_healthy", return_value=False) - def test_has_errors(self, _h: MagicMock, _f: MagicMock) -> None: + def test_has_errors(self, h: MagicMock, f: MagicMock) -> None: with patch(f"{MOD}.Path") as mock_path: mock_log = MagicMock() mock_log.exists.return_value = True mock_log.read_text.return_value = "log content" mock_path.return_value = mock_log - has_errors, msg = _check_cups_backend_errors("B") + has_errors, _ = _check_cups_backend_errors("B") assert has_errors is True @patch( @@ -427,32 +427,32 @@ class TestCheckCupsBackendErrors: return_value=("error", "2025-01-01", "2025-01-02"), ) @patch(f"{MOD}._is_cups_printer_healthy", return_value=False) - def test_success_after_error(self, _h: MagicMock, _f: MagicMock) -> None: + def test_success_after_error(self, h: MagicMock, f: MagicMock) -> None: with patch(f"{MOD}.Path") as mock_path: mock_log = MagicMock() mock_log.exists.return_value = True mock_log.read_text.return_value = "log content" mock_path.return_value = mock_log - has_errors, msg = _check_cups_backend_errors("B") + has_errors, _ = _check_cups_backend_errors("B") assert has_errors is False @patch(f"{MOD}._is_cups_printer_healthy", return_value=False) - def test_oserror_reading_log(self, _h: MagicMock) -> None: + def test_oserror_reading_log(self, h: MagicMock) -> None: with patch(f"{MOD}.Path") as mock_path: mock_log = MagicMock() mock_log.exists.return_value = True mock_log.read_text.side_effect = OSError("fail") mock_path.return_value = mock_log - has_errors, msg = _check_cups_backend_errors("B") + has_errors, _ = _check_cups_backend_errors("B") assert has_errors is False @patch(f"{MOD}._find_backend_error_in_log", return_value=("", "", "")) @patch(f"{MOD}._is_cups_printer_healthy", return_value=False) - def test_no_backend_error_in_log(self, _h: MagicMock, _f: MagicMock) -> None: + def test_no_backend_error_in_log(self, h: MagicMock, f: MagicMock) -> None: with patch(f"{MOD}.Path") as mock_path: mock_log = MagicMock() mock_log.exists.return_value = True mock_log.read_text.return_value = "clean log" mock_path.return_value = mock_log - has_errors, msg = _check_cups_backend_errors("B") + has_errors, _ = _check_cups_backend_errors("B") assert has_errors is False diff --git a/python_pkg/brother_printer/tests/test_cups_queue_part2.py b/python_pkg/brother_printer/tests/test_cups_queue_part2.py index 0e27a5a..a179177 100644 --- a/python_pkg/brother_printer/tests/test_cups_queue_part2.py +++ b/python_pkg/brother_printer/tests/test_cups_queue_part2.py @@ -30,7 +30,7 @@ class TestOfferQueueFix: @patch(f"{MOD}._handle_disabled_with_jobs") @patch(f"{MOD}._prompt", return_value="1") - def test_disabled_with_jobs(self, _p: MagicMock, mock_handler: MagicMock) -> None: + def test_disabled_with_jobs(self, p: MagicMock, mock_handler: MagicMock) -> None: queue = CUPSQueueStatus( printer_name="B", enabled=False, @@ -42,7 +42,7 @@ class TestOfferQueueFix: @patch(f"{MOD}._handle_disabled_no_jobs") @patch(f"{MOD}._prompt", return_value="2") - def test_disabled_no_jobs(self, _p: MagicMock, mock_handler: MagicMock) -> None: + def test_disabled_no_jobs(self, p: MagicMock, mock_handler: MagicMock) -> None: queue = CUPSQueueStatus(printer_name="B", enabled=False) with patch("sys.stdout", new_callable=StringIO): _offer_queue_fix(queue) @@ -50,7 +50,7 @@ class TestOfferQueueFix: @patch(f"{MOD}._handle_enabled_with_jobs") @patch(f"{MOD}._prompt", return_value="1") - def test_enabled_with_jobs(self, _p: MagicMock, mock_handler: MagicMock) -> None: + def test_enabled_with_jobs(self, p: MagicMock, mock_handler: MagicMock) -> None: queue = CUPSQueueStatus( printer_name="B", enabled=True, @@ -62,7 +62,7 @@ class TestOfferQueueFix: @patch(f"{MOD}._handle_backend_errors_only") @patch(f"{MOD}._prompt", return_value="1") - def test_backend_errors_only(self, _p: MagicMock, mock_handler: MagicMock) -> None: + def test_backend_errors_only(self, p: MagicMock, mock_handler: MagicMock) -> None: queue = CUPSQueueStatus(printer_name="B", enabled=True) with patch("sys.stdout", new_callable=StringIO): _offer_queue_fix(queue) @@ -74,12 +74,12 @@ class TestOfferQueueFix: class TestDwjEnableOnly: @patch(f"{MOD}._cups_enable_printer", return_value=True) - def test_success(self, _m: MagicMock) -> None: + def test_success(self, m: MagicMock) -> None: with patch("sys.stdout", new_callable=StringIO): _dwj_enable_only("B") @patch(f"{MOD}._cups_enable_printer", return_value=False) - def test_failure(self, _m: MagicMock) -> None: + def test_failure(self, m: MagicMock) -> None: with patch("sys.stdout", new_callable=StringIO): _dwj_enable_only("B") @@ -87,37 +87,37 @@ class TestDwjEnableOnly: class TestDwjCancelAndEnable: @patch(f"{MOD}._cups_enable_printer", return_value=True) @patch(f"{MOD}._cups_cancel_all_jobs", return_value=True) - def test_success(self, _c: MagicMock, _e: MagicMock) -> None: + def test_success(self, c: MagicMock, e: MagicMock) -> None: with patch("sys.stdout", new_callable=StringIO): _dwj_cancel_and_enable("B") @patch(f"{MOD}._cups_enable_printer", return_value=False) @patch(f"{MOD}._cups_cancel_all_jobs", return_value=True) - def test_enable_fails(self, _c: MagicMock, _e: MagicMock) -> None: + def test_enable_fails(self, c: MagicMock, e: MagicMock) -> None: with patch("sys.stdout", new_callable=StringIO): _dwj_cancel_and_enable("B") class TestDwjCancelOnly: @patch(f"{MOD}._cups_cancel_all_jobs", return_value=True) - def test_success(self, _m: MagicMock) -> None: + def test_success(self, m: MagicMock) -> None: with patch("sys.stdout", new_callable=StringIO): _dwj_cancel_only("B") @patch(f"{MOD}._cups_cancel_all_jobs", return_value=False) - def test_failure(self, _m: MagicMock) -> None: + def test_failure(self, m: MagicMock) -> None: with patch("sys.stdout", new_callable=StringIO): _dwj_cancel_only("B") class TestDwjRestartOnly: @patch(f"{MOD}._cups_restart_service", return_value=True) - def test_success(self, _m: MagicMock) -> None: + def test_success(self, m: MagicMock) -> None: with patch("sys.stdout", new_callable=StringIO): _dwj_restart_only("B") @patch(f"{MOD}._cups_restart_service", return_value=False) - def test_failure(self, _m: MagicMock) -> None: + def test_failure(self, m: MagicMock) -> None: with patch("sys.stdout", new_callable=StringIO): _dwj_restart_only("B") @@ -125,12 +125,12 @@ class TestDwjRestartOnly: class TestDwjRestartAndEnable: @patch(f"{MOD}._cups_enable_printer", return_value=True) @patch(f"{MOD}._cups_restart_service", return_value=True) - def test_success(self, _r: MagicMock, _e: MagicMock) -> None: + def test_success(self, r: MagicMock, e: MagicMock) -> None: with patch("sys.stdout", new_callable=StringIO): _dwj_restart_and_enable("B") @patch(f"{MOD}._cups_restart_service", return_value=False) - def test_restart_fails(self, _r: MagicMock) -> None: + def test_restart_fails(self, r: MagicMock) -> None: with patch("sys.stdout", new_callable=StringIO): _dwj_restart_and_enable("B") @@ -149,29 +149,29 @@ class TestHandleDisabledWithJobs: ) @patch(f"{MOD}._cups_enable_printer", return_value=True) - def test_choice_1(self, _m: MagicMock) -> None: + def test_choice_1(self, m: MagicMock) -> None: with patch("sys.stdout", new_callable=StringIO): _handle_disabled_with_jobs(self._make_queue(), "1") @patch(f"{MOD}._cups_enable_printer", return_value=True) @patch(f"{MOD}._cups_cancel_all_jobs", return_value=True) - def test_choice_2(self, _c: MagicMock, _e: MagicMock) -> None: + def test_choice_2(self, c: MagicMock, e: MagicMock) -> None: with patch("sys.stdout", new_callable=StringIO): _handle_disabled_with_jobs(self._make_queue(), "2") @patch(f"{MOD}._cups_cancel_all_jobs", return_value=True) - def test_choice_3(self, _m: MagicMock) -> None: + def test_choice_3(self, m: MagicMock) -> None: with patch("sys.stdout", new_callable=StringIO): _handle_disabled_with_jobs(self._make_queue(), "3") @patch(f"{MOD}._cups_restart_service", return_value=True) - def test_choice_4(self, _m: MagicMock) -> None: + def test_choice_4(self, m: MagicMock) -> None: with patch("sys.stdout", new_callable=StringIO): _handle_disabled_with_jobs(self._make_queue(), "4") @patch(f"{MOD}._cups_enable_printer", return_value=True) @patch(f"{MOD}._cups_restart_service", return_value=True) - def test_choice_5(self, _r: MagicMock, _e: MagicMock) -> None: + def test_choice_5(self, r: MagicMock, e: MagicMock) -> None: with patch("sys.stdout", new_callable=StringIO): _handle_disabled_with_jobs(self._make_queue(), "5") @@ -194,23 +194,23 @@ class TestHandleDisabledNoJobs: return CUPSQueueStatus(printer_name="B", enabled=False) @patch(f"{MOD}._cups_enable_printer", return_value=True) - def test_choice_1_enable(self, _m: MagicMock) -> None: + def test_choice_1_enable(self, m: MagicMock) -> None: with patch("sys.stdout", new_callable=StringIO): _handle_disabled_no_jobs(self._make_queue(), "1") @patch(f"{MOD}._cups_enable_printer", return_value=False) - def test_choice_1_enable_fails(self, _m: MagicMock) -> None: + def test_choice_1_enable_fails(self, m: MagicMock) -> None: with patch("sys.stdout", new_callable=StringIO): _handle_disabled_no_jobs(self._make_queue(), "1") @patch(f"{MOD}._cups_enable_printer", return_value=True) @patch(f"{MOD}._cups_restart_service", return_value=True) - def test_choice_2_restart(self, _r: MagicMock, _e: MagicMock) -> None: + def test_choice_2_restart(self, r: MagicMock, e: MagicMock) -> None: with patch("sys.stdout", new_callable=StringIO): _handle_disabled_no_jobs(self._make_queue(), "2") @patch(f"{MOD}._cups_restart_service", return_value=False) - def test_choice_2_restart_fails(self, _r: MagicMock) -> None: + def test_choice_2_restart_fails(self, r: MagicMock) -> None: with patch("sys.stdout", new_callable=StringIO): _handle_disabled_no_jobs(self._make_queue(), "2") @@ -233,22 +233,22 @@ class TestHandleEnabledWithJobs: ) @patch(f"{MOD}._cups_cancel_all_jobs", return_value=True) - def test_choice_1_cancel(self, _m: MagicMock) -> None: + def test_choice_1_cancel(self, m: MagicMock) -> None: with patch("sys.stdout", new_callable=StringIO): _handle_enabled_with_jobs(self._make_queue(), "1") @patch(f"{MOD}._cups_cancel_all_jobs", return_value=False) - def test_choice_1_cancel_fails(self, _m: MagicMock) -> None: + def test_choice_1_cancel_fails(self, m: MagicMock) -> None: with patch("sys.stdout", new_callable=StringIO): _handle_enabled_with_jobs(self._make_queue(), "1") @patch(f"{MOD}._cups_restart_service", return_value=True) - def test_choice_2_restart(self, _m: MagicMock) -> None: + def test_choice_2_restart(self, m: MagicMock) -> None: with patch("sys.stdout", new_callable=StringIO): _handle_enabled_with_jobs(self._make_queue(), "2") @patch(f"{MOD}._cups_restart_service", return_value=False) - def test_choice_2_restart_fails(self, _m: MagicMock) -> None: + def test_choice_2_restart_fails(self, m: MagicMock) -> None: with patch("sys.stdout", new_callable=StringIO): _handle_enabled_with_jobs(self._make_queue(), "2") @@ -264,12 +264,12 @@ class TestHandleBackendErrorsOnly: """Tests for _handle_backend_errors_only.""" @patch(f"{MOD}._cups_restart_service", return_value=True) - def test_choice_1_restart(self, _m: MagicMock) -> None: + def test_choice_1_restart(self, m: MagicMock) -> None: with patch("sys.stdout", new_callable=StringIO): _handle_backend_errors_only("1") @patch(f"{MOD}._cups_restart_service", return_value=False) - def test_choice_1_restart_fails(self, _m: MagicMock) -> None: + def test_choice_1_restart_fails(self, m: MagicMock) -> None: with patch("sys.stdout", new_callable=StringIO): _handle_backend_errors_only("1") diff --git a/python_pkg/brother_printer/tests/test_cups_service.py b/python_pkg/brother_printer/tests/test_cups_service.py index d509fc9..f868b90 100644 --- a/python_pkg/brother_printer/tests/test_cups_service.py +++ b/python_pkg/brother_printer/tests/test_cups_service.py @@ -88,68 +88,68 @@ class TestGetPyusbDeviceInfo: class TestStopCups: @patch(f"{MOD}.shutil.which", return_value=None) - def test_no_systemctl(self, _m: MagicMock) -> None: + def test_no_systemctl(self, m: MagicMock) -> None: assert _stop_cups() is False @patch(f"{MOD}.time.sleep") @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/systemctl") - def test_success(self, _w: MagicMock, mock_run: MagicMock, _s: MagicMock) -> None: + def test_success(self, w: MagicMock, mock_run: MagicMock, s: MagicMock) -> None: mock_run.return_value = MagicMock() assert _stop_cups() is True @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/systemctl") - def test_timeout(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_timeout(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.side_effect = subprocess.TimeoutExpired("systemctl", 15) assert _stop_cups() is False @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/systemctl") - def test_called_process_error(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_called_process_error(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.side_effect = subprocess.CalledProcessError(1, "systemctl") assert _stop_cups() is False @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/systemctl") - def test_oserror(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_oserror(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.side_effect = OSError("fail") assert _stop_cups() is False class TestIsCupsSchedulerRunning: @patch(f"{MOD}.shutil.which", return_value=None) - def test_no_lpstat(self, _m: MagicMock) -> None: + def test_no_lpstat(self, m: MagicMock) -> None: assert is_cups_scheduler_running() is False @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpstat") - def test_running(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_running(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.return_value = MagicMock(stdout="scheduler is running") assert is_cups_scheduler_running() is True @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpstat") - def test_not_running(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_not_running(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.return_value = MagicMock(stdout="scheduler is not running") assert is_cups_scheduler_running() is False @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpstat") - def test_timeout(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_timeout(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.side_effect = subprocess.TimeoutExpired("lpstat", 3) assert is_cups_scheduler_running() is False @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpstat") - def test_oserror(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_oserror(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.side_effect = OSError("fail") assert is_cups_scheduler_running() is False class TestStartCups: @patch(f"{MOD}.shutil.which", return_value=None) - def test_no_systemctl(self, _m: MagicMock) -> None: + def test_no_systemctl(self, m: MagicMock) -> None: assert start_cups() is False @patch(f"{MOD}.time.sleep") @@ -158,10 +158,10 @@ class TestStartCups: @patch(f"{MOD}.shutil.which", return_value="/usr/bin/systemctl") def test_success( self, - _w: MagicMock, + w: MagicMock, mock_run: MagicMock, mock_is_running: MagicMock, - _s: MagicMock, + s: MagicMock, ) -> None: mock_run.return_value = MagicMock() mock_is_running.return_value = True @@ -169,13 +169,13 @@ class TestStartCups: @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/systemctl") - def test_timeout(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_timeout(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.side_effect = subprocess.TimeoutExpired("systemctl", 15) assert start_cups() is False @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/systemctl") - def test_called_process_error(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_called_process_error(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.side_effect = subprocess.CalledProcessError(1, "systemctl") assert start_cups() is False @@ -185,10 +185,10 @@ class TestStartCups: @patch(f"{MOD}.shutil.which", return_value="/usr/bin/systemctl") def test_never_starts( self, - _w: MagicMock, + w: MagicMock, mock_run: MagicMock, - _is: MagicMock, - _s: MagicMock, + is_running: MagicMock, + s: MagicMock, ) -> None: mock_run.return_value = MagicMock() assert start_cups() is False @@ -196,33 +196,35 @@ class TestStartCups: class TestEnsureCupsRunning: @patch(f"{MOD}.is_cups_scheduler_running", return_value=True) - def test_already_running(self, _m: MagicMock) -> None: + def test_already_running(self, m: MagicMock) -> None: assert _ensure_cups_running() is True @patch(f"{MOD}.start_cups", return_value=True) @patch(f"{MOD}.is_cups_scheduler_running", return_value=False) - def test_needs_start(self, _is: MagicMock, _st: MagicMock) -> None: + def test_needs_start(self, is_running: MagicMock, st: MagicMock) -> None: assert _ensure_cups_running() is True @patch(f"{MOD}.start_cups", return_value=False) @patch(f"{MOD}.is_cups_scheduler_running", return_value=False) - def test_start_fails(self, _is: MagicMock, _st: MagicMock) -> None: + def test_start_fails(self, is_running: MagicMock, st: MagicMock) -> None: assert _ensure_cups_running() is False class TestQueryUsbPortStatusRaw: def test_import_error(self) -> None: - with patch(f"{MOD}._stop_cups"): + with ( + patch(f"{MOD}._stop_cups"), # Simulate ImportError for usb.core - with patch.dict( + patch.dict( "sys.modules", {"usb": None, "usb.core": None, "usb.util": None} - ): - result = _query_usb_port_status_raw() - assert result is None + ), + ): + result = _query_usb_port_status_raw() + assert result is None @patch(f"{MOD}.start_cups") @patch(f"{MOD}._stop_cups", return_value=False) - def test_stop_cups_fails(self, _st: MagicMock, _s: MagicMock) -> None: + def test_stop_cups_fails(self, st: MagicMock, s: MagicMock) -> None: import sys as _sys mock_usb = MagicMock() @@ -236,7 +238,7 @@ class TestQueryUsbPortStatusRaw: @patch(f"{MOD}.start_cups") @patch(f"{MOD}._stop_cups", return_value=True) - def test_dev_none_after_reset(self, _st: MagicMock, _s: MagicMock) -> None: + def test_dev_none_after_reset(self, st: MagicMock, s: MagicMock) -> None: import sys as _sys mock_usb = MagicMock() @@ -254,7 +256,7 @@ class TestQueryUsbPortStatusRaw: @patch(f"{MOD}.start_cups") @patch(f"{MOD}._stop_cups", return_value=True) - def test_success(self, _stop: MagicMock, _start: MagicMock) -> None: + def test_success(self, stop: MagicMock, start: MagicMock) -> None: import sys as _sys mock_usb = MagicMock() @@ -276,9 +278,7 @@ class TestQueryUsbPortStatusRaw: @patch(f"{MOD}.start_cups") @patch(f"{MOD}._stop_cups", return_value=True) - def test_kernel_driver_not_active( - self, _stop: MagicMock, _start: MagicMock - ) -> None: + def test_kernel_driver_not_active(self, stop: MagicMock, start: MagicMock) -> None: import sys as _sys mock_usb = MagicMock() @@ -299,7 +299,7 @@ class TestQueryUsbPortStatusRaw: @patch(f"{MOD}.start_cups") @patch(f"{MOD}._stop_cups", return_value=True) - def test_kernel_driver_usberror(self, _stop: MagicMock, _start: MagicMock) -> None: + def test_kernel_driver_usberror(self, stop: MagicMock, start: MagicMock) -> None: import sys as _sys mock_usb = MagicMock() @@ -321,7 +321,7 @@ class TestQueryUsbPortStatusRaw: @patch(f"{MOD}.start_cups") @patch(f"{MOD}._stop_cups", return_value=True) - def test_oserror_during_transfer(self, _stop: MagicMock, _start: MagicMock) -> None: + def test_oserror_during_transfer(self, stop: MagicMock, start: MagicMock) -> None: import sys as _sys mock_usb = MagicMock() @@ -342,7 +342,7 @@ class TestQueryUsbPortStatusRaw: @patch(f"{MOD}.start_cups") @patch(f"{MOD}._stop_cups", return_value=True) - def test_dev_none_initial(self, _stop: MagicMock, _start: MagicMock) -> None: + def test_dev_none_initial(self, stop: MagicMock, start: MagicMock) -> None: import sys as _sys mock_usb = MagicMock() @@ -443,12 +443,12 @@ class TestResetConsumable: @patch(f"{MOD}._get_cups_total_pages", return_value=500) def test_reset_toner( self, - _pages: MagicMock, - _load: MagicMock, + pages: MagicMock, + load: MagicMock, mock_save: MagicMock, - _out: MagicMock, + out: MagicMock, ) -> None: - _load.return_value = {"toner_replaced_at": 0, "drum_replaced_at": 0} + load.return_value = {"toner_replaced_at": 0, "drum_replaced_at": 0} reset_consumable("toner") saved_state = mock_save.call_args[0][0] assert saved_state["toner_replaced_at"] == 500 diff --git a/python_pkg/brother_printer/tests/test_cups_service_part2.py b/python_pkg/brother_printer/tests/test_cups_service_part2.py index ae8cbcb..5a04cc8 100644 --- a/python_pkg/brother_printer/tests/test_cups_service_part2.py +++ b/python_pkg/brother_printer/tests/test_cups_service_part2.py @@ -28,12 +28,12 @@ class TestGetCupsEconomode: """Tests for _get_cups_economode.""" @patch(f"{MOD}.shutil.which", return_value=None) - def test_no_lpoptions(self, _m: MagicMock) -> None: + def test_no_lpoptions(self, m: MagicMock) -> None: assert _get_cups_economode("Brother") == "" @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpoptions") - def test_economode_on(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_economode_on(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.return_value = MagicMock( stdout="BREconomode/Toner Save Mode: *True False\n" ) @@ -41,7 +41,7 @@ class TestGetCupsEconomode: @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpoptions") - def test_economode_off(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_economode_off(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.return_value = MagicMock( stdout="BREconomode/Toner Save Mode: True *False\n" ) @@ -49,7 +49,7 @@ class TestGetCupsEconomode: @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpoptions") - def test_no_economode_line(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_no_economode_line(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.return_value = MagicMock( stdout="Resolution/Output Resolution: 600dpi *1200dpi\n" ) @@ -57,7 +57,7 @@ class TestGetCupsEconomode: @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpoptions") - def test_economode_no_star_match(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_economode_no_star_match(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.return_value = MagicMock( stdout="BREconomode/Toner Save Mode: True False\n" ) @@ -65,13 +65,13 @@ class TestGetCupsEconomode: @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpoptions") - def test_timeout(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_timeout(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.side_effect = subprocess.TimeoutExpired("lpoptions", 5) assert _get_cups_economode("Brother") == "" @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpoptions") - def test_oserror(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_oserror(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.side_effect = OSError("fail") assert _get_cups_economode("Brother") == "" @@ -119,19 +119,19 @@ class TestCupsReasonsToError: assert display == "Paper Jam" def test_cover_open(self) -> None: - code, display = _cups_reasons_to_error("cover-open") + code, _ = _cups_reasons_to_error("cover-open") assert code == "41000" def test_door_open(self) -> None: - code, display = _cups_reasons_to_error("door-open") + code, _ = _cups_reasons_to_error("door-open") assert code == "41000" def test_toner_empty(self) -> None: - code, display = _cups_reasons_to_error("toner-empty") + code, _ = _cups_reasons_to_error("toner-empty") assert code == "40310" def test_toner_low(self) -> None: - code, display = _cups_reasons_to_error("toner-low") + code, _ = _cups_reasons_to_error("toner-low") assert code == "30010" def test_unknown_reason(self) -> None: @@ -160,12 +160,12 @@ class TestPortStatusToStatusCode: def test_error_only(self) -> None: ps = USBPortStatus(error=True, paper_empty=False, online=True) - code, display = _port_status_to_status_code(ps, "media-jam") + code, _ = _port_status_to_status_code(ps, "media-jam") assert code == "40000" def test_paper_empty_no_error(self) -> None: ps = USBPortStatus(error=False, paper_empty=True, online=True) - code, display = _port_status_to_status_code(ps, "none") + code, _ = _port_status_to_status_code(ps, "none") assert code == "40302" def test_not_online_no_error(self) -> None: @@ -188,12 +188,12 @@ class TestFindCupsPrinterName: """Tests for find_cups_printer_name.""" @patch(f"{MOD}.shutil.which", return_value=None) - def test_no_lpstat(self, _m: MagicMock) -> None: + def test_no_lpstat(self, m: MagicMock) -> None: assert find_cups_printer_name() == "" @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpstat") - def test_found(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_found(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.return_value = MagicMock( stdout="device for BrotherHL1110: usb://Brother/HL-1110\n" ) @@ -201,13 +201,13 @@ class TestFindCupsPrinterName: @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpstat") - def test_no_brother(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_no_brother(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.return_value = MagicMock(stdout="device for HP: ipp://hp.local\n") assert find_cups_printer_name() == "" @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpstat") - def test_brother_no_match(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_brother_no_match(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.return_value = MagicMock( stdout="brother printer found but format unexpected\n" ) @@ -215,13 +215,13 @@ class TestFindCupsPrinterName: @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpstat") - def test_timeout(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_timeout(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.side_effect = subprocess.TimeoutExpired("lpstat", 5) assert find_cups_printer_name() == "" @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpstat") - def test_oserror(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_oserror(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.side_effect = OSError("fail") assert find_cups_printer_name() == "" diff --git a/python_pkg/brother_printer/tests/test_cups_service_part3.py b/python_pkg/brother_printer/tests/test_cups_service_part3.py index ddb203a..15f3655 100644 --- a/python_pkg/brother_printer/tests/test_cups_service_part3.py +++ b/python_pkg/brother_printer/tests/test_cups_service_part3.py @@ -23,286 +23,234 @@ class TestQueryUsbViaCups: @patch(f"{MOD}.find_cups_printer_name", return_value="") @patch(f"{MOD}._ensure_cups_running", return_value=True) - def test_no_printer(self, _e: MagicMock, _f: MagicMock) -> None: + def test_no_printer(self, e: MagicMock, f: MagicMock) -> None: result = query_usb_via_cups() assert result.error != "" - @patch(f"{MOD}._query_usb_port_status_raw", return_value=None) - @patch(f"{MOD}._get_cups_economode", return_value="ON") - @patch( - f"{MOD}._get_cups_ipp_status", - return_value={ - "printer-state": "idle", - "printer-state-reasons": "none", - "printer-state-message": "Ready", - }, - ) - @patch( - f"{MOD}._get_printer_info_from_cups", - return_value={"product": "HL-1110", "serial": "ABC"}, - ) - @patch(f"{MOD}._get_pyusb_device_info", return_value={}) - @patch(f"{MOD}.find_cups_printer_name", return_value="Brother") - @patch(f"{MOD}._ensure_cups_running", return_value=True) - def test_no_port_status_idle( - self, - _e: MagicMock, - _f: MagicMock, - _py: MagicMock, - _cups: MagicMock, - _ipp: MagicMock, - _eco: MagicMock, - _port: MagicMock, - ) -> None: - result = query_usb_via_cups() - assert result.online == "TRUE" - assert result.product == "HL-1110" - assert result.economode == "ON" + def test_no_port_status_idle(self) -> None: + with ( + patch(f"{MOD}._ensure_cups_running", return_value=True), + patch(f"{MOD}.find_cups_printer_name", return_value="Brother"), + patch(f"{MOD}._get_pyusb_device_info", return_value={}), + patch( + f"{MOD}._get_printer_info_from_cups", + return_value={"product": "HL-1110", "serial": "ABC"}, + ), + patch( + f"{MOD}._get_cups_ipp_status", + return_value={ + "printer-state": "idle", + "printer-state-reasons": "none", + "printer-state-message": "Ready", + }, + ), + patch(f"{MOD}._get_cups_economode", return_value="ON"), + patch(f"{MOD}._query_usb_port_status_raw", return_value=None), + ): + result = query_usb_via_cups() + assert result.online == "TRUE" + assert result.product == "HL-1110" + assert result.economode == "ON" - @patch(f"{MOD}._query_usb_port_status_raw", return_value=None) - @patch(f"{MOD}._get_cups_economode", return_value="") - @patch( - f"{MOD}._get_cups_ipp_status", - return_value={ - "printer-state": "stopped", - "printer-state-reasons": "none", - }, - ) - @patch( - f"{MOD}._get_printer_info_from_cups", - return_value={"product": "", "serial": ""}, - ) - @patch(f"{MOD}._get_pyusb_device_info", return_value={}) - @patch(f"{MOD}.find_cups_printer_name", return_value="Brother") - @patch(f"{MOD}._ensure_cups_running", return_value=True) - def test_no_port_status_stopped( - self, - _e: MagicMock, - _f: MagicMock, - _py: MagicMock, - _cups: MagicMock, - _ipp: MagicMock, - _eco: MagicMock, - _port: MagicMock, - ) -> None: - result = query_usb_via_cups() - assert result.online == "FALSE" - assert result.product == "Brother Laser Printer" + def test_no_port_status_stopped(self) -> None: + with ( + patch(f"{MOD}._ensure_cups_running", return_value=True), + patch(f"{MOD}.find_cups_printer_name", return_value="Brother"), + patch(f"{MOD}._get_pyusb_device_info", return_value={}), + patch( + f"{MOD}._get_printer_info_from_cups", + return_value={"product": "", "serial": ""}, + ), + patch( + f"{MOD}._get_cups_ipp_status", + return_value={ + "printer-state": "stopped", + "printer-state-reasons": "none", + }, + ), + patch(f"{MOD}._get_cups_economode", return_value=""), + patch(f"{MOD}._query_usb_port_status_raw", return_value=None), + ): + result = query_usb_via_cups() + assert result.online == "FALSE" + assert result.product == "Brother Laser Printer" - @patch( - f"{MOD}._query_usb_port_status_raw", - return_value=USBPortStatus( - error=True, - paper_empty=True, - online=False, - raw_byte=0x20, - ), - ) - @patch(f"{MOD}._get_cups_economode", return_value="") - @patch( - f"{MOD}._get_cups_ipp_status", - return_value={ - "printer-state": "stopped", - "printer-state-reasons": "none", - }, - ) - @patch( - f"{MOD}._get_printer_info_from_cups", - return_value={"product": "", "serial": ""}, - ) - @patch(f"{MOD}._get_pyusb_device_info", return_value={}) - @patch(f"{MOD}.find_cups_printer_name", return_value="Brother") - @patch(f"{MOD}._ensure_cups_running", return_value=True) - def test_port_status_hw_error( - self, - _e: MagicMock, - _f: MagicMock, - _py: MagicMock, - _cups: MagicMock, - _ipp: MagicMock, - _eco: MagicMock, - _port: MagicMock, - ) -> None: - result = query_usb_via_cups() - assert result.status_code == "40302" - assert result.online == "FALSE" + def test_port_status_hw_error(self) -> None: + with ( + patch(f"{MOD}._ensure_cups_running", return_value=True), + patch(f"{MOD}.find_cups_printer_name", return_value="Brother"), + patch(f"{MOD}._get_pyusb_device_info", return_value={}), + patch( + f"{MOD}._get_printer_info_from_cups", + return_value={"product": "", "serial": ""}, + ), + patch( + f"{MOD}._get_cups_ipp_status", + return_value={ + "printer-state": "stopped", + "printer-state-reasons": "none", + }, + ), + patch(f"{MOD}._get_cups_economode", return_value=""), + patch( + f"{MOD}._query_usb_port_status_raw", + return_value=USBPortStatus( + error=True, + paper_empty=True, + online=False, + raw_byte=0x20, + ), + ), + ): + result = query_usb_via_cups() + assert result.status_code == "40302" + assert result.online == "FALSE" - @patch( - f"{MOD}.estimate_consumable_life", - return_value=PageCountEstimate( - toner_exhausted=True, - total_pages=1000, - toner_pages=1000, - ), - ) - @patch( - f"{MOD}._query_usb_port_status_raw", - return_value=USBPortStatus( - error=False, - paper_empty=False, - online=True, - raw_byte=0x18, - ), - ) - @patch(f"{MOD}._get_cups_economode", return_value="") - @patch( - f"{MOD}._get_cups_ipp_status", - return_value={ - "printer-state": "idle", - "printer-state-reasons": "none", - }, - ) - @patch( - f"{MOD}._get_printer_info_from_cups", - return_value={"product": "", "serial": ""}, - ) - @patch(f"{MOD}._get_pyusb_device_info", return_value={}) - @patch(f"{MOD}.find_cups_printer_name", return_value="Brother") - @patch(f"{MOD}._ensure_cups_running", return_value=True) - def test_port_ok_toner_exhausted( - self, - _e: MagicMock, - _f: MagicMock, - _py: MagicMock, - _cups: MagicMock, - _ipp: MagicMock, - _eco: MagicMock, - _port: MagicMock, - _est: MagicMock, - ) -> None: - result = query_usb_via_cups() - assert result.status_code == "40310" - assert "Toner End" in result.display + def test_port_ok_toner_exhausted(self) -> None: + with ( + patch(f"{MOD}._ensure_cups_running", return_value=True), + patch(f"{MOD}.find_cups_printer_name", return_value="Brother"), + patch(f"{MOD}._get_pyusb_device_info", return_value={}), + patch( + f"{MOD}._get_printer_info_from_cups", + return_value={"product": "", "serial": ""}, + ), + patch( + f"{MOD}._get_cups_ipp_status", + return_value={ + "printer-state": "idle", + "printer-state-reasons": "none", + }, + ), + patch(f"{MOD}._get_cups_economode", return_value=""), + patch( + f"{MOD}._query_usb_port_status_raw", + return_value=USBPortStatus( + error=False, + paper_empty=False, + online=True, + raw_byte=0x18, + ), + ), + patch( + f"{MOD}.estimate_consumable_life", + return_value=PageCountEstimate( + toner_exhausted=True, + total_pages=1000, + toner_pages=1000, + ), + ), + ): + result = query_usb_via_cups() + assert result.status_code == "40310" + assert "Toner End" in result.display - @patch( - f"{MOD}.estimate_consumable_life", - return_value=PageCountEstimate( - toner_low=True, - total_pages=800, - toner_pages=800, - ), - ) - @patch( - f"{MOD}._query_usb_port_status_raw", - return_value=USBPortStatus( - error=False, - paper_empty=False, - online=True, - raw_byte=0x18, - ), - ) - @patch(f"{MOD}._get_cups_economode", return_value="") - @patch( - f"{MOD}._get_cups_ipp_status", - return_value={ - "printer-state": "idle", - "printer-state-reasons": "none", - }, - ) - @patch( - f"{MOD}._get_printer_info_from_cups", - return_value={"product": "", "serial": ""}, - ) - @patch(f"{MOD}._get_pyusb_device_info", return_value={}) - @patch(f"{MOD}.find_cups_printer_name", return_value="Brother") - @patch(f"{MOD}._ensure_cups_running", return_value=True) - def test_port_ok_toner_low( - self, - _e: MagicMock, - _f: MagicMock, - _py: MagicMock, - _cups: MagicMock, - _ipp: MagicMock, - _eco: MagicMock, - _port: MagicMock, - _est: MagicMock, - ) -> None: - result = query_usb_via_cups() - assert result.status_code == "30010" - assert "Toner Low" in result.display + def test_port_ok_toner_low(self) -> None: + with ( + patch(f"{MOD}._ensure_cups_running", return_value=True), + patch(f"{MOD}.find_cups_printer_name", return_value="Brother"), + patch(f"{MOD}._get_pyusb_device_info", return_value={}), + patch( + f"{MOD}._get_printer_info_from_cups", + return_value={"product": "", "serial": ""}, + ), + patch( + f"{MOD}._get_cups_ipp_status", + return_value={ + "printer-state": "idle", + "printer-state-reasons": "none", + }, + ), + patch(f"{MOD}._get_cups_economode", return_value=""), + patch( + f"{MOD}._query_usb_port_status_raw", + return_value=USBPortStatus( + error=False, + paper_empty=False, + online=True, + raw_byte=0x18, + ), + ), + patch( + f"{MOD}.estimate_consumable_life", + return_value=PageCountEstimate( + toner_low=True, + total_pages=800, + toner_pages=800, + ), + ), + ): + result = query_usb_via_cups() + assert result.status_code == "30010" + assert "Toner Low" in result.display - @patch( - f"{MOD}.estimate_consumable_life", - return_value=PageCountEstimate(total_pages=100, toner_pages=100), - ) - @patch( - f"{MOD}._query_usb_port_status_raw", - return_value=USBPortStatus( - error=False, - paper_empty=False, - online=True, - raw_byte=0x18, - ), - ) - @patch(f"{MOD}._get_cups_economode", return_value="") - @patch( - f"{MOD}._get_cups_ipp_status", - return_value={ - "printer-state": "idle", - "printer-state-reasons": "none", - "printer-state-message": "Ready", - }, - ) - @patch( - f"{MOD}._get_printer_info_from_cups", - return_value={"product": "", "serial": ""}, - ) - @patch(f"{MOD}._get_pyusb_device_info", return_value={}) - @patch(f"{MOD}.find_cups_printer_name", return_value="Brother") - @patch(f"{MOD}._ensure_cups_running", return_value=True) - def test_port_ok_normal( - self, - _e: MagicMock, - _f: MagicMock, - _py: MagicMock, - _cups: MagicMock, - _ipp: MagicMock, - _eco: MagicMock, - _port: MagicMock, - _est: MagicMock, - ) -> None: - result = query_usb_via_cups() - assert result.online == "TRUE" - assert result.display == "Ready" + def test_port_ok_normal(self) -> None: + with ( + patch(f"{MOD}._ensure_cups_running", return_value=True), + patch(f"{MOD}.find_cups_printer_name", return_value="Brother"), + patch(f"{MOD}._get_pyusb_device_info", return_value={}), + patch( + f"{MOD}._get_printer_info_from_cups", + return_value={"product": "", "serial": ""}, + ), + patch( + f"{MOD}._get_cups_ipp_status", + return_value={ + "printer-state": "idle", + "printer-state-reasons": "none", + "printer-state-message": "Ready", + }, + ), + patch(f"{MOD}._get_cups_economode", return_value=""), + patch( + f"{MOD}._query_usb_port_status_raw", + return_value=USBPortStatus( + error=False, + paper_empty=False, + online=True, + raw_byte=0x18, + ), + ), + patch( + f"{MOD}.estimate_consumable_life", + return_value=PageCountEstimate(total_pages=100, toner_pages=100), + ), + ): + result = query_usb_via_cups() + assert result.online == "TRUE" + assert result.display == "Ready" - @patch( - f"{MOD}._query_usb_port_status_raw", - return_value=USBPortStatus( - error=True, - paper_empty=False, - online=True, - raw_byte=0x00, - ), - ) - @patch(f"{MOD}._get_cups_economode", return_value="") - @patch( - f"{MOD}._get_cups_ipp_status", - return_value={ - "printer-state": "stopped", - "printer-state-reasons": "media-jam", - }, - ) - @patch( - f"{MOD}._get_printer_info_from_cups", - return_value={"product": "", "serial": ""}, - ) - @patch( - f"{MOD}._get_pyusb_device_info", - return_value={"product": "HL-1110", "serial": "SN1"}, - ) - @patch(f"{MOD}.find_cups_printer_name", return_value="Brother") - @patch(f"{MOD}._ensure_cups_running", return_value=True) - def test_port_error_uses_cups_reasons( - self, - _e: MagicMock, - _f: MagicMock, - _py: MagicMock, - _cups: MagicMock, - _ipp: MagicMock, - _eco: MagicMock, - _port: MagicMock, - ) -> None: - result = query_usb_via_cups() - assert result.status_code == "40000" - assert result.product == "HL-1110" - assert result.online == "TRUE" + def test_port_error_uses_cups_reasons(self) -> None: + with ( + patch(f"{MOD}._ensure_cups_running", return_value=True), + patch(f"{MOD}.find_cups_printer_name", return_value="Brother"), + patch( + f"{MOD}._get_pyusb_device_info", + return_value={"product": "HL-1110", "serial": "SN1"}, + ), + patch( + f"{MOD}._get_printer_info_from_cups", + return_value={"product": "", "serial": ""}, + ), + patch( + f"{MOD}._get_cups_ipp_status", + return_value={ + "printer-state": "stopped", + "printer-state-reasons": "media-jam", + }, + ), + patch(f"{MOD}._get_cups_economode", return_value=""), + patch( + f"{MOD}._query_usb_port_status_raw", + return_value=USBPortStatus( + error=True, + paper_empty=False, + online=True, + raw_byte=0x00, + ), + ), + ): + result = query_usb_via_cups() + assert result.status_code == "40000" + assert result.product == "HL-1110" + assert result.online == "TRUE" diff --git a/python_pkg/brother_printer/tests/test_cups_service_part4.py b/python_pkg/brother_printer/tests/test_cups_service_part4.py index 88978dd..ae50cee 100644 --- a/python_pkg/brother_printer/tests/test_cups_service_part4.py +++ b/python_pkg/brother_printer/tests/test_cups_service_part4.py @@ -17,13 +17,13 @@ MOD = "python_pkg.brother_printer.cups_service" class TestEstimateConsumableLife: @patch(f"{MOD}._load_consumable_state") @patch(f"{MOD}._get_cups_total_pages", return_value=0) - def test_no_pages(self, _p: MagicMock, _l: MagicMock) -> None: + def test_no_pages(self, p: MagicMock, mock_load: MagicMock) -> None: result = estimate_consumable_life() assert result.total_pages == 0 @patch(f"{MOD}._load_consumable_state") @patch(f"{MOD}._get_cups_total_pages", return_value=500) - def test_mid_life(self, _p: MagicMock, mock_load: MagicMock) -> None: + def test_mid_life(self, p: MagicMock, mock_load: MagicMock) -> None: mock_load.return_value = {"toner_replaced_at": 0, "drum_replaced_at": 0} result = estimate_consumable_life() assert result.total_pages == 500 @@ -33,21 +33,21 @@ class TestEstimateConsumableLife: @patch(f"{MOD}._load_consumable_state") @patch(f"{MOD}._get_cups_total_pages", return_value=1000) - def test_toner_exhausted(self, _p: MagicMock, mock_load: MagicMock) -> None: + def test_toner_exhausted(self, p: MagicMock, mock_load: MagicMock) -> None: mock_load.return_value = {"toner_replaced_at": 0, "drum_replaced_at": 0} result = estimate_consumable_life() assert result.toner_exhausted is True @patch(f"{MOD}._load_consumable_state") @patch(f"{MOD}._get_cups_total_pages", return_value=800) - def test_toner_low(self, _p: MagicMock, mock_load: MagicMock) -> None: + def test_toner_low(self, p: MagicMock, mock_load: MagicMock) -> None: mock_load.return_value = {"toner_replaced_at": 0, "drum_replaced_at": 0} result = estimate_consumable_life() assert result.toner_low is True @patch(f"{MOD}._load_consumable_state") @patch(f"{MOD}._get_cups_total_pages", return_value=9000) - def test_drum_near_end(self, _p: MagicMock, mock_load: MagicMock) -> None: + def test_drum_near_end(self, p: MagicMock, mock_load: MagicMock) -> None: mock_load.return_value = {"toner_replaced_at": 8500, "drum_replaced_at": 0} result = estimate_consumable_life() assert result.drum_near_end is True @@ -67,12 +67,12 @@ class TestParseIppAttributes: class TestGetCupsIppStatus: @patch(f"{MOD}.shutil.which", return_value=None) - def test_no_ipptool(self, _m: MagicMock) -> None: + def test_no_ipptool(self, m: MagicMock) -> None: assert _get_cups_ipp_status("Brother") == {} @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/ipptool") - def test_success(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_success(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.return_value = MagicMock( stdout=" printer-state (enum) = idle\n", ) @@ -81,6 +81,6 @@ class TestGetCupsIppStatus: @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/ipptool") - def test_timeout(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_timeout(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.side_effect = subprocess.TimeoutExpired("ipptool", 10) assert _get_cups_ipp_status("Brother") == {} diff --git a/python_pkg/brother_printer/tests/test_display.py b/python_pkg/brother_printer/tests/test_display.py index b71821f..057ed90 100644 --- a/python_pkg/brother_printer/tests/test_display.py +++ b/python_pkg/brother_printer/tests/test_display.py @@ -227,7 +227,7 @@ class TestDisplayPjlStatus: @patch(f"{MOD}._format_status_detail") @patch(f"{MOD}.get_status_info", return_value=("ok", "Ready", "")) - def test_with_code(self, _g: MagicMock, mock_fmt: MagicMock) -> None: + def test_with_code(self, g: MagicMock, mock_fmt: MagicMock) -> None: r = USBResult(status_code="10001") with patch("sys.stdout", new_callable=StringIO): _display_pjl_status(r) @@ -249,50 +249,35 @@ class TestDisplayCupsFallbackNote: class TestDisplayUsbResults: - @patch(f"{MOD}.display_cups_queue_status") - @patch(f"{MOD}.get_cups_queue_status") - @patch(f"{MOD}._display_consumables_reference") - @patch(f"{MOD}._display_page_count_estimate") - @patch(f"{MOD}._display_pjl_status") - @patch(f"{MOD}._display_usb_device_info") - @patch(f"{MOD}._display_report_header") - def test_normal( - self, - _h: MagicMock, - _d: MagicMock, - _p: MagicMock, - _pe: MagicMock, - _c: MagicMock, - _gq: MagicMock, - _dq: MagicMock, - ) -> None: + def test_normal(self) -> None: r = USBResult(device="/dev/usb/lp0") - with patch("sys.stdout", new_callable=StringIO): + with ( + patch(f"{MOD}._display_report_header"), + patch(f"{MOD}._display_usb_device_info"), + patch(f"{MOD}._display_pjl_status"), + patch(f"{MOD}._display_page_count_estimate"), + patch(f"{MOD}._display_consumables_reference"), + patch(f"{MOD}.get_cups_queue_status"), + patch(f"{MOD}.display_cups_queue_status"), + patch("sys.stdout", new_callable=StringIO), + ): display_usb_results(r) - @patch(f"{MOD}._display_cups_fallback_note") - @patch(f"{MOD}.display_cups_queue_status") - @patch(f"{MOD}.get_cups_queue_status") - @patch(f"{MOD}._display_consumables_reference") - @patch(f"{MOD}._display_page_count_estimate") - @patch(f"{MOD}._display_pjl_status") - @patch(f"{MOD}._display_usb_device_info") - @patch(f"{MOD}._display_report_header") - def test_cups_device( - self, - _h: MagicMock, - _d: MagicMock, - _p: MagicMock, - _pe: MagicMock, - _c: MagicMock, - _gq: MagicMock, - _dq: MagicMock, - mock_fallback: MagicMock, - ) -> None: + def test_cups_device(self) -> None: r = USBResult(device="cups") - with patch("sys.stdout", new_callable=StringIO): + with ( + patch(f"{MOD}._display_report_header"), + patch(f"{MOD}._display_usb_device_info"), + patch(f"{MOD}._display_pjl_status"), + patch(f"{MOD}._display_page_count_estimate"), + patch(f"{MOD}._display_consumables_reference"), + patch(f"{MOD}.get_cups_queue_status"), + patch(f"{MOD}.display_cups_queue_status"), + patch(f"{MOD}._display_cups_fallback_note") as mock_fallback, + patch("sys.stdout", new_callable=StringIO), + ): display_usb_results(r) - mock_fallback.assert_called_once() + mock_fallback.assert_called_once() def test_error(self) -> None: r = USBResult(error="fail") @@ -305,49 +290,49 @@ class TestDisplayUsbResults: class TestClassifyPercentageLevel: def test_low(self) -> None: - pct, text, color, warn, replace = _classify_percentage_level("Toner", 5) + pct, _, _, _, replace = _classify_percentage_level("Toner", 5) assert pct == 5 assert replace is True def test_warn(self) -> None: - pct, text, color, warn, replace = _classify_percentage_level("Toner", 20) + _, _, _, warn, replace = _classify_percentage_level("Toner", 20) assert replace is False assert "order soon" in warn def test_ok(self) -> None: - pct, text, color, warn, replace = _classify_percentage_level("Toner", 80) + _, _, _, warn, replace = _classify_percentage_level("Toner", 80) assert replace is False assert warn == "" class TestClassifySupplyLevel: def test_snmp_ok(self) -> None: - pct, text, color, warn, replace = _classify_supply_level("Toner", 100, -3) + _, text, _, _, replace = _classify_supply_level("Toner", 100, -3) assert text == "OK" assert replace is False def test_snmp_low(self) -> None: - pct, text, color, warn, replace = _classify_supply_level("Toner", 100, -2) + _, text, _, _, replace = _classify_supply_level("Toner", 100, -2) assert text == "LOW" assert replace is True def test_empty(self) -> None: - pct, text, color, warn, replace = _classify_supply_level("Toner", 100, 0) + _, text, _, _, replace = _classify_supply_level("Toner", 100, 0) assert text == "EMPTY" assert replace is True def test_normal_percentage(self) -> None: - pct, text, color, warn, replace = _classify_supply_level("Toner", 100, 80) + pct, _, _, _, replace = _classify_supply_level("Toner", 100, 80) assert pct == 80 assert replace is False def test_no_max_val(self) -> None: - pct, text, color, warn, replace = _classify_supply_level("Toner", 0, 50) + pct, text, _, _, _ = _classify_supply_level("Toner", 0, 50) assert pct == -1 assert text == "" def test_over_100_capped(self) -> None: - pct, text, color, warn, replace = _classify_supply_level("Toner", 50, 100) + pct, _, _, _, _ = _classify_supply_level("Toner", 50, 100) assert pct == 100 diff --git a/python_pkg/brother_printer/tests/test_display_part2.py b/python_pkg/brother_printer/tests/test_display_part2.py index 21ed37e..930ba9c 100644 --- a/python_pkg/brother_printer/tests/test_display_part2.py +++ b/python_pkg/brother_printer/tests/test_display_part2.py @@ -72,9 +72,9 @@ class TestDisplayNetworkResults: @patch(f"{MOD}._display_report_header") def test_normal( self, - _h: MagicMock, - _d: MagicMock, - _s: MagicMock, + h: MagicMock, + d: MagicMock, + s: MagicMock, ) -> None: r = NetworkResult(ip="1.2.3.4") with patch("sys.stdout", new_callable=StringIO) as out: diff --git a/python_pkg/brother_printer/tests/test_network_query.py b/python_pkg/brother_printer/tests/test_network_query.py index 73dc7f0..404b02a 100644 --- a/python_pkg/brother_printer/tests/test_network_query.py +++ b/python_pkg/brother_printer/tests/test_network_query.py @@ -49,7 +49,7 @@ class TestSnmpgetCmd: class TestSnmpWalk: @patch("python_pkg.brother_printer.network_query.shutil.which", return_value=None) - def test_no_snmpwalk(self, _mock: MagicMock) -> None: + def test_no_snmpwalk(self, mock: MagicMock) -> None: assert snmp_walk("1.2.3.4", "1.3.6", "public", 5) == [] @patch("python_pkg.brother_printer.network_query.subprocess.run") @@ -57,7 +57,7 @@ class TestSnmpWalk: "python_pkg.brother_printer.network_query.shutil.which", return_value="/usr/bin/snmpwalk", ) - def test_success(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_success(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.return_value = MagicMock( stdout=' "Brother HL-1110" \n "SN123" \n', ) @@ -69,7 +69,7 @@ class TestSnmpWalk: "python_pkg.brother_printer.network_query.shutil.which", return_value="/usr/bin/snmpwalk", ) - def test_empty_lines_stripped(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_empty_lines_stripped(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.return_value = MagicMock(stdout=" \n value \n \n") result = snmp_walk("1.2.3.4", "1.3.6", "public", 5) assert result == ["value"] @@ -79,7 +79,7 @@ class TestSnmpWalk: "python_pkg.brother_printer.network_query.shutil.which", return_value="/usr/bin/snmpwalk", ) - def test_timeout(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_timeout(self, w: MagicMock, mock_run: MagicMock) -> None: import subprocess mock_run.side_effect = subprocess.TimeoutExpired("snmpwalk", 15) @@ -90,7 +90,7 @@ class TestSnmpWalk: "python_pkg.brother_printer.network_query.shutil.which", return_value="/usr/bin/snmpwalk", ) - def test_oserror(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_oserror(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.side_effect = OSError("fail") assert snmp_walk("1.2.3.4", "1.3.6", "public", 5) == [] @@ -100,7 +100,7 @@ class TestCheckSnmpConnectivity: "python_pkg.brother_printer.network_query.shutil.which", return_value=None, ) - def test_no_snmpget(self, _mock: MagicMock) -> None: + def test_no_snmpget(self, mock: MagicMock) -> None: result = _check_snmp_connectivity("1.2.3.4", "public", 5) assert result is not None assert "snmpget not found" in result @@ -110,7 +110,7 @@ class TestCheckSnmpConnectivity: "python_pkg.brother_printer.network_query.shutil.which", return_value="/usr/bin/snmpget", ) - def test_success(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_success(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.return_value = MagicMock() assert _check_snmp_connectivity("1.2.3.4", "public", 5) is None @@ -119,7 +119,7 @@ class TestCheckSnmpConnectivity: "python_pkg.brother_printer.network_query.shutil.which", return_value="/usr/bin/snmpget", ) - def test_timeout(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_timeout(self, w: MagicMock, mock_run: MagicMock) -> None: import subprocess mock_run.side_effect = subprocess.TimeoutExpired("snmpget", 10) @@ -132,7 +132,7 @@ class TestCheckSnmpConnectivity: "python_pkg.brother_printer.network_query.shutil.which", return_value="/usr/bin/snmpget", ) - def test_called_process_error(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_called_process_error(self, w: MagicMock, mock_run: MagicMock) -> None: import subprocess mock_run.side_effect = subprocess.CalledProcessError(1, "snmpget") @@ -144,7 +144,7 @@ class TestCheckSnmpConnectivity: "python_pkg.brother_printer.network_query.shutil.which", return_value="/usr/bin/snmpget", ) - def test_oserror(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_oserror(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.side_effect = OSError("fail") result = _check_snmp_connectivity("1.2.3.4", "public", 5) assert result is not None @@ -172,7 +172,7 @@ class TestQueryNetworkSnmp: "python_pkg.brother_printer.network_query._check_snmp_connectivity", return_value=None, ) - def test_success(self, _c: MagicMock, mock_build: MagicMock) -> None: + def test_success(self, c: MagicMock, mock_build: MagicMock) -> None: from python_pkg.brother_printer.data_classes import NetworkResult mock_build.return_value = NetworkResult(ip="1.2.3.4") @@ -184,6 +184,6 @@ class TestQueryNetworkSnmp: "python_pkg.brother_printer.network_query._check_snmp_connectivity", return_value="Error msg", ) - def test_connectivity_error(self, _c: MagicMock) -> None: + def test_connectivity_error(self, c: MagicMock) -> None: result = query_network_snmp("1.2.3.4") assert result.error == "Error msg" diff --git a/python_pkg/brother_printer/tests/test_usb_query.py b/python_pkg/brother_printer/tests/test_usb_query.py index dcb11ba..73b3643 100644 --- a/python_pkg/brother_printer/tests/test_usb_query.py +++ b/python_pkg/brother_printer/tests/test_usb_query.py @@ -27,12 +27,12 @@ MOD = "python_pkg.brother_printer.usb_query" class TestFindBrotherUsb: @patch(f"{MOD}.shutil.which", return_value=None) - def test_no_lsusb(self, _m: MagicMock) -> None: + def test_no_lsusb(self, m: MagicMock) -> None: assert find_brother_usb() == "" @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/lsusb") - def test_found(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_found(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.return_value = MagicMock( stdout="Bus 001 Device 005: ID 04f9:0042 Brother Industries\n", ) @@ -41,13 +41,13 @@ class TestFindBrotherUsb: @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/lsusb") - def test_not_found(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_not_found(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.return_value = MagicMock(stdout="Bus 001 Device 001: Hub\n") assert find_brother_usb() == "" @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/lsusb") - def test_line_with_colon_sep(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_line_with_colon_sep(self, w: MagicMock, mock_run: MagicMock) -> None: """Line contains 04f9: but no ': ' separator → returns full line.""" mock_run.return_value = MagicMock(stdout="ID 04f9:0042\n") result = find_brother_usb() @@ -55,14 +55,14 @@ class TestFindBrotherUsb: @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/lsusb") - def test_no_match(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_no_match(self, w: MagicMock, mock_run: MagicMock) -> None: """Line without 04f9: vendor id is ignored.""" mock_run.return_value = MagicMock(stdout="04f9 brother no colon\n") assert find_brother_usb() == "" @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/lsusb") - def test_timeout(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_timeout(self, w: MagicMock, mock_run: MagicMock) -> None: import subprocess mock_run.side_effect = subprocess.TimeoutExpired("lsusb", 5) @@ -70,7 +70,7 @@ class TestFindBrotherUsb: @patch(f"{MOD}.subprocess.run") @patch(f"{MOD}.shutil.which", return_value="/usr/bin/lsusb") - def test_oserror(self, _w: MagicMock, mock_run: MagicMock) -> None: + def test_oserror(self, w: MagicMock, mock_run: MagicMock) -> None: mock_run.side_effect = OSError("fail") assert find_brother_usb() == "" @@ -79,9 +79,9 @@ class TestFindUsbPrinterDev: @patch(f"{MOD}.Path") def test_found(self, mock_path_cls: MagicMock) -> None: mock_path_cls.return_value = mock_path_cls - mock_path_cls.__truediv__ = lambda self, x: mock_path_cls + mock_path_cls.__truediv__ = lambda _self, _x: mock_path_cls lp0 = MagicMock() - lp0.__str__ = lambda s: "/dev/usb/lp0" + lp0.__str__ = lambda _s: "/dev/usb/lp0" lp0.__lt__ = lambda s, o: str(s) < str(o) mock_usb = MagicMock() mock_usb.glob.return_value = [lp0] @@ -295,7 +295,7 @@ class TestPjlQuery: @patch(f"{MOD}.time.time", return_value=100.0) def test_query( self, - _t: MagicMock, + t: MagicMock, mock_fcntl: MagicMock, mock_write: MagicMock, mock_wait: MagicMock, @@ -345,8 +345,8 @@ class TestRetryPjlQuery: def test_success_first_attempt( self, mock_pjl: MagicMock, - _d: MagicMock, - _s: MagicMock, + d: MagicMock, + s: MagicMock, ) -> None: result = USBResult() mock_pjl.return_value = "CODE=10001\n" @@ -360,8 +360,8 @@ class TestRetryPjlQuery: def test_retry_then_success( self, mock_pjl: MagicMock, - _d: MagicMock, - _s: MagicMock, + d: MagicMock, + s: MagicMock, ) -> None: result = USBResult() mock_pjl.side_effect = ["garbage\n", "CODE=10001\n"] @@ -375,8 +375,8 @@ class TestRetryPjlQuery: def test_all_retries_fail( self, mock_pjl: MagicMock, - _d: MagicMock, - _s: MagicMock, + d: MagicMock, + s: MagicMock, ) -> None: result = USBResult() mock_pjl.return_value = "garbage\n" @@ -393,8 +393,8 @@ class TestRunPjlQueries: def test_runs_both_queries( self, mock_write: MagicMock, - _d: MagicMock, - _s: MagicMock, + d: MagicMock, + s: MagicMock, mock_retry: MagicMock, ) -> None: result = USBResult() @@ -419,29 +419,22 @@ class TestInitUsbResult: class TestQueryUsbPjl: - @patch(f"{MOD}.os.close") - @patch(f"{MOD}._run_pjl_queries") - @patch(f"{MOD}.fcntl.fcntl", return_value=0) - @patch(f"{MOD}.os.open", return_value=10) - @patch(f"{MOD}.os.access", return_value=True) - @patch(f"{MOD}._init_usb_result") - @patch(f"{MOD}.find_usb_printer_dev", return_value="/dev/usb/lp0") - def test_success( - self, - _f: MagicMock, - mock_init: MagicMock, - _a: MagicMock, - _o: MagicMock, - _fc: MagicMock, - _r: MagicMock, - _c: MagicMock, - ) -> None: - mock_init.return_value = USBResult(device="/dev/usb/lp0") - result = query_usb_pjl() - assert result.device == "/dev/usb/lp0" + def test_success(self) -> None: + with ( + patch(f"{MOD}.find_usb_printer_dev", return_value="/dev/usb/lp0"), + patch(f"{MOD}._init_usb_result") as mock_init, + patch(f"{MOD}.os.access", return_value=True), + patch(f"{MOD}.os.open", return_value=10), + patch(f"{MOD}.fcntl.fcntl", return_value=0), + patch(f"{MOD}._run_pjl_queries"), + patch(f"{MOD}.os.close"), + ): + mock_init.return_value = USBResult(device="/dev/usb/lp0") + result = query_usb_pjl() + assert result.device == "/dev/usb/lp0" @patch(f"{MOD}.find_usb_printer_dev", return_value=None) - def test_no_dev_falls_back_to_cups(self, _f: MagicMock) -> None: + def test_no_dev_falls_back_to_cups(self, f: MagicMock) -> None: with patch( "python_pkg.brother_printer.cups_service.query_usb_via_cups", ) as mock_cups: @@ -454,32 +447,26 @@ class TestQueryUsbPjl: @patch(f"{MOD}.find_usb_printer_dev", return_value="/dev/usb/lp0") def test_permission_denied( self, - _f: MagicMock, + f: MagicMock, mock_init: MagicMock, - _a: MagicMock, + a: MagicMock, ) -> None: mock_init.return_value = USBResult(device="/dev/usb/lp0") result = query_usb_pjl() assert "Permission denied" in result.error - @patch(f"{MOD}.os.close") - @patch(f"{MOD}.fcntl.fcntl", side_effect=OSError("bad fd")) - @patch(f"{MOD}.os.open", return_value=10) - @patch(f"{MOD}.os.access", return_value=True) - @patch(f"{MOD}._init_usb_result") - @patch(f"{MOD}.find_usb_printer_dev", return_value="/dev/usb/lp0") - def test_oserror_on_open( - self, - _f: MagicMock, - mock_init: MagicMock, - _a: MagicMock, - _o: MagicMock, - _fc: MagicMock, - _c: MagicMock, - ) -> None: - mock_init.return_value = USBResult(device="/dev/usb/lp0") - result = query_usb_pjl() - assert result.error != "" + def test_oserror_on_open(self) -> None: + with ( + patch(f"{MOD}.find_usb_printer_dev", return_value="/dev/usb/lp0"), + patch(f"{MOD}._init_usb_result") as mock_init, + patch(f"{MOD}.os.access", return_value=True), + patch(f"{MOD}.os.open", return_value=10), + patch(f"{MOD}.fcntl.fcntl", side_effect=OSError("bad fd")), + patch(f"{MOD}.os.close"), + ): + mock_init.return_value = USBResult(device="/dev/usb/lp0") + result = query_usb_pjl() + assert result.error != "" @patch(f"{MOD}.os.open", side_effect=OSError("no device")) @patch(f"{MOD}.os.access", return_value=True) @@ -487,10 +474,10 @@ class TestQueryUsbPjl: @patch(f"{MOD}.find_usb_printer_dev", return_value="/dev/usb/lp0") def test_oserror_fd_none( self, - _f: MagicMock, + f: MagicMock, mock_init: MagicMock, - _a: MagicMock, - _o: MagicMock, + a: MagicMock, + o: MagicMock, ) -> None: """os.open raises OSError before fd is set → fd stays None.""" mock_init.return_value = USBResult(device="/dev/usb/lp0") diff --git a/python_pkg/brother_printer/usb_query.py b/python_pkg/brother_printer/usb_query.py index c56be0d..e914668 100644 --- a/python_pkg/brother_printer/usb_query.py +++ b/python_pkg/brother_printer/usb_query.py @@ -4,6 +4,7 @@ from __future__ import annotations import contextlib import fcntl +import importlib import os from pathlib import Path import select @@ -211,9 +212,10 @@ def query_usb_pjl(max_retries: int = 2) -> USBResult: """Query a Brother printer via PJL over /dev/usb/lp*.""" dev_path = find_usb_printer_dev() if not dev_path: - from python_pkg.brother_printer.cups_service import query_usb_via_cups - - return query_usb_via_cups() + cups_service = importlib.import_module( + "python_pkg.brother_printer.cups_service", + ) + return cups_service.query_usb_via_cups() result = _init_usb_result(dev_path) if not os.access(dev_path, os.R_OK | os.W_OK): diff --git a/python_pkg/cinema_planner/tests/test_cinema_parsing.py b/python_pkg/cinema_planner/tests/test_cinema_parsing.py index b7131e8..d1c9eec 100644 --- a/python_pkg/cinema_planner/tests/test_cinema_parsing.py +++ b/python_pkg/cinema_planner/tests/test_cinema_parsing.py @@ -4,7 +4,7 @@ from __future__ import annotations from pathlib import Path import subprocess -from typing import Any +from typing import TYPE_CHECKING from unittest.mock import MagicMock, mock_open, patch import pytest @@ -24,6 +24,9 @@ from python_pkg.cinema_planner._cinema_parsing import ( parse_time, ) +if TYPE_CHECKING: + import contextlib + class TestParseTime: """Tests for parse_time.""" @@ -208,13 +211,13 @@ class TestParseCinemaCityHtml: f"{times_html}" ) - def _patch_open(self, html: str) -> Any: + def _patch_open(self, html: str) -> contextlib.AbstractContextManager[MagicMock]: return patch.object(Path, "open", mock_open(read_data=html)) def test_parse_single_movie(self) -> None: html = "header" + self._make_html_section("Movie A", 120, ["10:00", "14:00"]) with self._patch_open(html): - movies, date = parse_cinema_city_html("test.html") + movies, _ = parse_cinema_city_html("test.html") assert len(movies) == 1 assert movies[0].name == "Movie A" assert movies[0].duration == 120 @@ -223,7 +226,7 @@ class TestParseCinemaCityHtml: def test_parse_with_date(self) -> None: html = "2025-01-25 stuff" + self._make_html_section("Movie A", 90, ["18:00"]) with self._patch_open(html): - movies, date = parse_cinema_city_html("test.html") + _, date = parse_cinema_city_html("test.html") assert date == "2025-01-25" def test_parse_with_genres(self) -> None: @@ -231,7 +234,7 @@ class TestParseCinemaCityHtml: "Horror Film", 100, ["20:00"], genre="Horror, Thriller" ) with self._patch_open(html): - movies, date = parse_cinema_city_html("test.html") + movies, _ = parse_cinema_city_html("test.html") assert len(movies) == 1 assert "Horror" in movies[0].genres assert "Thriller" in movies[0].genres @@ -239,7 +242,7 @@ class TestParseCinemaCityHtml: def test_no_name_match(self) -> None: html = 'header class="row movie-row"> no name here' with self._patch_open(html): - movies, date = parse_cinema_city_html("test.html") + movies, _ = parse_cinema_city_html("test.html") assert len(movies) == 0 def test_no_duration_match(self) -> None: @@ -250,7 +253,7 @@ class TestParseCinemaCityHtml: '' ) with self._patch_open(html): - movies, date = parse_cinema_city_html("test.html") + movies, _ = parse_cinema_city_html("test.html") assert len(movies) == 0 def test_no_times_match(self) -> None: @@ -260,7 +263,7 @@ class TestParseCinemaCityHtml: "100 min" ) with self._patch_open(html): - movies, date = parse_cinema_city_html("test.html") + movies, _ = parse_cinema_city_html("test.html") assert len(movies) == 0 def test_alternate_time_pattern(self) -> None: @@ -271,7 +274,7 @@ class TestParseCinemaCityHtml: "> 10:00 (HTTPS://something" ) with self._patch_open(html): - movies, date = parse_cinema_city_html("test.html") + movies, _ = parse_cinema_city_html("test.html") assert len(movies) == 1 def test_deduplicate_movies(self) -> None: @@ -467,7 +470,7 @@ class TestParseCinemaCityText: text = "MOVIE TITLE\n110 min\n10:00\n" with patch( "python_pkg.cinema_planner._cinema_parsing._try_parse_time", - side_effect=lambda t: None, + side_effect=lambda _t: None, ): result = parse_cinema_city_text(text) assert len(result) == 0 diff --git a/python_pkg/cinema_planner/tests/test_cinema_planner.py b/python_pkg/cinema_planner/tests/test_cinema_planner.py index aca12b6..61741a3 100644 --- a/python_pkg/cinema_planner/tests/test_cinema_planner.py +++ b/python_pkg/cinema_planner/tests/test_cinema_planner.py @@ -5,7 +5,6 @@ from __future__ import annotations import argparse from io import StringIO from pathlib import Path -from typing import Any from unittest.mock import MagicMock, mock_open, patch import pytest @@ -94,7 +93,7 @@ class TestLoadMoviesInteractive: """Tests for _load_movies_interactive.""" @patch("builtins.input", side_effect=["Movie A, 10:00, 90min", ""]) - def test_single_movie(self, _mock: MagicMock) -> None: + def test_single_movie(self, mock: MagicMock) -> None: result = _load_movies_interactive() assert len(result) == 1 assert result[0].name == "Movie A" @@ -107,17 +106,17 @@ class TestLoadMoviesInteractive: "", ], ) - def test_multiple_movies(self, _mock: MagicMock) -> None: + def test_multiple_movies(self, mock: MagicMock) -> None: result = _load_movies_interactive() assert len(result) == 2 @patch("builtins.input", side_effect=EOFError) - def test_eof(self, _mock: MagicMock) -> None: + def test_eof(self, mock: MagicMock) -> None: result = _load_movies_interactive() assert result == [] @patch("builtins.input", side_effect=["bad line", ""]) - def test_invalid_input(self, _mock: MagicMock) -> None: + def test_invalid_input(self, mock: MagicMock) -> None: result = _load_movies_interactive() assert result == [] @@ -125,7 +124,7 @@ class TestLoadMoviesInteractive: "builtins.input", side_effect=["bad line", "Movie A, 10:00, 90min", ""], ) - def test_mixed_valid_invalid(self, _mock: MagicMock) -> None: + def test_mixed_valid_invalid(self, mock: MagicMock) -> None: result = _load_movies_interactive() assert len(result) == 1 @@ -147,7 +146,7 @@ class TestLoadMoviesFromFile: ) def test_htm_file(self, mock_parse: MagicMock) -> None: mock_parse.return_value = ([Movie("A", [600], 120)], None) - movies, date = _load_movies_from_file(Path("test.htm")) + _, _ = _load_movies_from_file(Path("test.htm")) mock_parse.assert_called_once() @patch( @@ -161,17 +160,21 @@ class TestLoadMoviesFromFile: def test_text_file(self) -> None: content = "Movie A, 10:00, 90min\n# comment\nMovie B, 14:00, 120min\n" - with patch.object(Path, "open", mock_open(read_data=content)): - with patch.object(Path, "suffix", new=".txt"): - movies, date = _load_movies_from_file(Path("test.txt")) + with ( + patch.object(Path, "open", mock_open(read_data=content)), + patch.object(Path, "suffix", new=".txt"), + ): + movies, date = _load_movies_from_file(Path("test.txt")) assert len(movies) == 2 assert date is None def test_text_file_with_bad_line(self) -> None: content = "Movie A, 10:00, 90min\nbad line\n" - with patch.object(Path, "open", mock_open(read_data=content)): - with patch.object(Path, "suffix", new=".txt"): - movies, date = _load_movies_from_file(Path("test.txt")) + with ( + patch.object(Path, "open", mock_open(read_data=content)), + patch.object(Path, "suffix", new=".txt"), + ): + movies, _ = _load_movies_from_file(Path("test.txt")) assert len(movies) == 1 @@ -192,7 +195,7 @@ class TestLoadMoviesFromStdin: class TestFilterMovies: """Tests for _filter_movies.""" - def _make_args(self, **kwargs: Any) -> argparse.Namespace: + def _make_args(self, **kwargs: str | bool | None) -> argparse.Namespace: defaults = { "select": None, "exclude": None, @@ -204,7 +207,7 @@ class TestFilterMovies: def test_no_filters(self) -> None: movies = [Movie("A", [600], 120)] - result, excluded = _filter_movies(movies, self._make_args()) + result, _ = _filter_movies(movies, self._make_args()) # Default horror exclusion but no genre matches assert len(result) == 1 @@ -259,7 +262,7 @@ class TestFilterMovies: Movie("Action Movie", [600], 120, ["Action"]), Movie("Drama Movie", [600], 120, ["Drama"]), ] - result, excluded = _filter_movies( + result, _ = _filter_movies( movies, self._make_args(all_genres=True, exclude_genre="action"), ) @@ -268,7 +271,7 @@ class TestFilterMovies: def test_no_genre_filtered(self) -> None: movies = [Movie("Movie", [600], 120, ["Comedy"])] - result, excluded = _filter_movies(movies, self._make_args()) + result, _ = _filter_movies(movies, self._make_args()) assert len(result) == 1 @@ -301,7 +304,7 @@ class TestApplyMustWatchFilter: class TestOutputSchedules: """Tests for _output_schedules.""" - def _make_args(self, **kwargs: Any) -> argparse.Namespace: + def _make_args(self, **kwargs: str | int | None) -> argparse.Namespace: defaults = { "buffer": 0, "max_schedules": 5, diff --git a/python_pkg/geo_data/_common.py b/python_pkg/geo_data/_common.py index 41aca7d..38e8e27 100644 --- a/python_pkg/geo_data/_common.py +++ b/python_pkg/geo_data/_common.py @@ -11,7 +11,6 @@ from pathlib import Path import sys import time from typing import TYPE_CHECKING -from urllib.request import urlopen import geopandas as gpd import requests @@ -172,8 +171,8 @@ def _download_github_geojson(url: str, cache_path: Path) -> gpd.GeoDataFrame: if not url.startswith(("http://", "https://")): msg = f"Unsupported URL scheme: {url}" raise ValueError(msg) - with urlopen(url, timeout=REQUEST_TIMEOUT) as response: - data = json.loads(response.read().decode()) + response = requests.get(url, timeout=REQUEST_TIMEOUT) + data = response.json() _ensure_cache_dir() cache_path.write_text(json.dumps(data)) diff --git a/python_pkg/geo_data/_poland_admin.py b/python_pkg/geo_data/_poland_admin.py index 10fb876..f6e5550 100644 --- a/python_pkg/geo_data/_poland_admin.py +++ b/python_pkg/geo_data/_poland_admin.py @@ -19,6 +19,7 @@ from python_pkg.geo_data._common import ( CACHE_DIR, POLSKA_GEOJSON_BASE, WIKIDATA_SPARQL, + _add_area_column, _build_osiedla_geometry, _download_github_geojson, _ensure_cache_dir, @@ -196,8 +197,6 @@ def get_polish_gminy() -> gpd.GeoDataFrame: gdf = gpd.GeoDataFrame.from_features(features, crs="EPSG:4326") # Add area column - from python_pkg.geo_data._common import _add_area_column - gdf = _add_area_column(gdf) return gdf.sort_values("area_km2", ascending=False).reset_index(drop=True) diff --git a/python_pkg/geo_data/tests/test_common.py b/python_pkg/geo_data/tests/test_common.py index 1895549..8f3f36c 100644 --- a/python_pkg/geo_data/tests/test_common.py +++ b/python_pkg/geo_data/tests/test_common.py @@ -2,7 +2,6 @@ from __future__ import annotations -import json from pathlib import Path from typing import Any from unittest.mock import MagicMock, patch @@ -220,12 +219,12 @@ class TestDownloadGithubGeojson: @patch("python_pkg.geo_data._common.gpd.GeoDataFrame.from_features") @patch("python_pkg.geo_data._common._ensure_cache_dir") - @patch("python_pkg.geo_data._common.urlopen") + @patch("python_pkg.geo_data._common.requests.get") @patch("python_pkg.geo_data._common.sys.stdout") def test_downloads_and_caches( self, mock_stdout: MagicMock, - mock_urlopen: MagicMock, + mock_get: MagicMock, mock_ensure: MagicMock, mock_from_features: MagicMock, ) -> None: @@ -239,10 +238,8 @@ class TestDownloadGithubGeojson: ] } mock_response = MagicMock() - mock_response.read.return_value = json.dumps(features_data).encode() - mock_response.__enter__ = MagicMock(return_value=mock_response) - mock_response.__exit__ = MagicMock(return_value=False) - mock_urlopen.return_value = mock_response + mock_response.json.return_value = features_data + mock_get.return_value = mock_response mock_gdf = MagicMock() mock_from_features.return_value = mock_gdf @@ -325,7 +322,7 @@ class TestExtractOsiedlaRings: } ] } - outer, inner = _extract_osiedla_rings(element, 4) + outer, _ = _extract_osiedla_rings(element, 4) assert len(outer) == 1 # Already closed, so no extra point assert outer[0][0] == outer[0][-1] diff --git a/python_pkg/geo_data/tests/test_init.py b/python_pkg/geo_data/tests/test_init.py index 47f7f1f..16132f7 100644 --- a/python_pkg/geo_data/tests/test_init.py +++ b/python_pkg/geo_data/tests/test_init.py @@ -14,33 +14,25 @@ from python_pkg.geo_data import ( class TestDownloadAllWarsawData: """Tests for download_all_warsaw_data.""" - @patch("python_pkg.geo_data.get_warsaw_osiedla") - @patch("python_pkg.geo_data.get_warsaw_landmarks") - @patch("python_pkg.geo_data.get_warsaw_streets") - @patch("python_pkg.geo_data.get_warsaw_metro_stations") - @patch("python_pkg.geo_data.get_warsaw_bridges") - @patch("python_pkg.geo_data.get_vistula_river") - @patch("python_pkg.geo_data.get_warsaw_boundary") - @patch("python_pkg.geo_data.sys.stdout") - def test_calls_all_warsaw_functions( - self, - mock_stdout: MagicMock, - mock_boundary: MagicMock, - mock_vistula: MagicMock, - mock_bridges: MagicMock, - mock_metro: MagicMock, - mock_streets: MagicMock, - mock_landmarks: MagicMock, - mock_osiedla: MagicMock, - ) -> None: - download_all_warsaw_data() - mock_boundary.assert_called_once() - mock_vistula.assert_called_once() - mock_bridges.assert_called_once() - mock_metro.assert_called_once() - mock_streets.assert_called_once() - mock_landmarks.assert_called_once() - mock_osiedla.assert_called_once() + def test_calls_all_warsaw_functions(self) -> None: + with ( + patch("python_pkg.geo_data.sys.stdout"), + patch("python_pkg.geo_data.get_warsaw_boundary") as mock_boundary, + patch("python_pkg.geo_data.get_vistula_river") as mock_vistula, + patch("python_pkg.geo_data.get_warsaw_bridges") as mock_bridges, + patch("python_pkg.geo_data.get_warsaw_metro_stations") as mock_metro, + patch("python_pkg.geo_data.get_warsaw_streets") as mock_streets, + patch("python_pkg.geo_data.get_warsaw_landmarks") as mock_landmarks, + patch("python_pkg.geo_data.get_warsaw_osiedla") as mock_osiedla, + ): + download_all_warsaw_data() + mock_boundary.assert_called_once() + mock_vistula.assert_called_once() + mock_bridges.assert_called_once() + mock_metro.assert_called_once() + mock_streets.assert_called_once() + mock_landmarks.assert_called_once() + mock_osiedla.assert_called_once() class TestDownloadAllPolandData: diff --git a/python_pkg/geo_data/tests/test_poland_admin.py b/python_pkg/geo_data/tests/test_poland_admin.py index 3d98e36..78309ab 100644 --- a/python_pkg/geo_data/tests/test_poland_admin.py +++ b/python_pkg/geo_data/tests/test_poland_admin.py @@ -195,81 +195,77 @@ class TestGetPolishGminy: result = get_polish_gminy() assert len(result) == 1 - @patch("python_pkg.geo_data._common._add_area_column") - @patch("python_pkg.geo_data._poland_admin.gpd.GeoDataFrame.from_features") - @patch("python_pkg.geo_data._poland_admin._ensure_cache_dir") - @patch("python_pkg.geo_data._poland_admin._overpass_query") - @patch("python_pkg.geo_data._poland_admin.CACHE_DIR") - @patch("python_pkg.geo_data._poland_admin.sys.stdout") - def test_downloads_from_osm( - self, - mock_stdout: MagicMock, - mock_cache_dir: MagicMock, - mock_query: MagicMock, - mock_ensure: MagicMock, - mock_from_features: MagicMock, - mock_add_area: MagicMock, - ) -> None: - mock_path = MagicMock() - mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) - mock_path.exists.return_value = False + def test_downloads_from_osm(self) -> None: + with ( + patch("python_pkg.geo_data._poland_admin.sys.stdout"), + patch("python_pkg.geo_data._poland_admin.CACHE_DIR") as mock_cache_dir, + patch("python_pkg.geo_data._poland_admin._overpass_query") as mock_query, + patch("python_pkg.geo_data._poland_admin._ensure_cache_dir"), + patch( + "python_pkg.geo_data._poland_admin.gpd.GeoDataFrame.from_features" + ) as mock_from_features, + patch("python_pkg.geo_data._common._add_area_column") as mock_add_area, + ): + mock_path = MagicMock() + mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) + mock_path.exists.return_value = False - mock_query.return_value = { - "elements": [ - { - "type": "relation", - "tags": {"name": "Gmina A"}, - "members": [ - { - "role": "outer", - "geometry": [ - {"lon": 0, "lat": 0}, - {"lon": 1, "lat": 0}, - {"lon": 1, "lat": 1}, - {"lon": 0, "lat": 1}, - ], - } - ], - }, - # Duplicate name - should be skipped - { - "type": "relation", - "tags": {"name": "Gmina A"}, - "members": [ - { - "role": "outer", - "geometry": [ - {"lon": 2, "lat": 2}, - {"lon": 3, "lat": 2}, - {"lon": 3, "lat": 3}, - {"lon": 2, "lat": 3}, - ], - } - ], - }, - # Not a relation - should be skipped - {"type": "way", "tags": {"name": "Way"}}, - # No name - {"type": "relation", "tags": {}}, - # No outer rings - { - "type": "relation", - "tags": {"name": "Empty"}, - "members": [], - }, - ] - } + mock_query.return_value = { + "elements": [ + { + "type": "relation", + "tags": {"name": "Gmina A"}, + "members": [ + { + "role": "outer", + "geometry": [ + {"lon": 0, "lat": 0}, + {"lon": 1, "lat": 0}, + {"lon": 1, "lat": 1}, + {"lon": 0, "lat": 1}, + ], + } + ], + }, + # Duplicate name - should be skipped + { + "type": "relation", + "tags": {"name": "Gmina A"}, + "members": [ + { + "role": "outer", + "geometry": [ + {"lon": 2, "lat": 2}, + {"lon": 3, "lat": 2}, + {"lon": 3, "lat": 3}, + {"lon": 2, "lat": 3}, + ], + } + ], + }, + # Not a relation - should be skipped + {"type": "way", "tags": {"name": "Way"}}, + # No name + {"type": "relation", "tags": {}}, + # No outer rings + { + "type": "relation", + "tags": {"name": "Empty"}, + "members": [], + }, + ] + } - mock_gdf = gpd.GeoDataFrame( - {"name": ["Gmina A"], "area_km2": [100.0]}, - geometry=[Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])], - crs="EPSG:4326", - ) - mock_from_features.return_value = mock_gdf - mock_add_area.return_value = mock_gdf + mock_gdf = gpd.GeoDataFrame( + {"name": ["Gmina A"], "area_km2": [100.0]}, + geometry=[Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])], + crs="EPSG:4326", + ) + mock_from_features.return_value = mock_gdf + mock_add_area.return_value = mock_gdf - result = get_polish_gminy() - assert len(result) == 1 + result = get_polish_gminy() + assert len(result) == 1 class TestGetPolandBoundary: diff --git a/python_pkg/geo_data/tests/test_poland_nature_part2.py b/python_pkg/geo_data/tests/test_poland_nature_part2.py index 3782589..ef62b4a 100644 --- a/python_pkg/geo_data/tests/test_poland_nature_part2.py +++ b/python_pkg/geo_data/tests/test_poland_nature_part2.py @@ -73,119 +73,115 @@ class TestGetPolishForests: result = get_polish_forests() assert len(result) == 1 - @patch("python_pkg.geo_data._poland_nature._add_area_column") - @patch("python_pkg.geo_data._poland_nature.gpd.GeoDataFrame.from_features") - @patch("python_pkg.geo_data._poland_nature._ensure_cache_dir") - @patch("python_pkg.geo_data._poland_nature._overpass_query") - @patch("python_pkg.geo_data._poland_nature.CACHE_DIR") - @patch("python_pkg.geo_data._poland_nature.sys.stdout") - def test_downloads_forests( - self, - mock_stdout: MagicMock, - mock_cache_dir: MagicMock, - mock_query: MagicMock, - mock_ensure: MagicMock, - mock_from_features: MagicMock, - mock_add_area: MagicMock, - ) -> None: - mock_path = MagicMock() - mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) - mock_path.exists.return_value = False + def test_downloads_forests(self) -> None: + with ( + patch("python_pkg.geo_data._poland_nature.sys.stdout"), + patch("python_pkg.geo_data._poland_nature.CACHE_DIR") as mock_cache_dir, + patch("python_pkg.geo_data._poland_nature._overpass_query") as mock_query, + patch("python_pkg.geo_data._poland_nature._ensure_cache_dir"), + patch( + "python_pkg.geo_data._poland_nature.gpd.GeoDataFrame.from_features" + ) as mock_from_features, + patch( + "python_pkg.geo_data._poland_nature._add_area_column" + ) as mock_add_area, + ): + mock_path = MagicMock() + mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) + mock_path.exists.return_value = False - mock_query.return_value = { - "elements": [ - # Valid forest with keyword - { - "type": "way", - "tags": {"name": "Puszcza Białowieska"}, - "geometry": [ - {"lon": 0, "lat": 0}, - {"lon": 1, "lat": 0}, - {"lon": 1, "lat": 1}, - {"lon": 0, "lat": 1}, - ], - }, - # Bory keyword - { - "type": "way", - "tags": {"name": "Bory Tucholskie"}, - "geometry": [ - {"lon": 2, "lat": 2}, - {"lon": 3, "lat": 2}, - {"lon": 3, "lat": 3}, - {"lon": 2, "lat": 3}, - ], - }, - # No forest keyword -> skip - { - "type": "way", - "tags": {"name": "Random Wood"}, - "geometry": [ - {"lon": 0, "lat": 0}, - {"lon": 1, "lat": 0}, - {"lon": 1, "lat": 1}, - {"lon": 0, "lat": 1}, - ], - }, - # Duplicate - { - "type": "way", - "tags": {"name": "Puszcza Białowieska"}, - "geometry": [ - {"lon": 0, "lat": 0}, - {"lon": 1, "lat": 0}, - {"lon": 1, "lat": 1}, - {"lon": 0, "lat": 1}, - ], - }, - # No name - {"type": "way", "tags": {}, "geometry": []}, - # Geometry extraction fails (too few coords) - { - "type": "way", - "tags": {"name": "Las Mały"}, - "geometry": [{"lon": 0, "lat": 0}], - }, - ] - } + mock_query.return_value = { + "elements": [ + # Valid forest with keyword + { + "type": "way", + "tags": {"name": "Puszcza Białowieska"}, + "geometry": [ + {"lon": 0, "lat": 0}, + {"lon": 1, "lat": 0}, + {"lon": 1, "lat": 1}, + {"lon": 0, "lat": 1}, + ], + }, + # Bory keyword + { + "type": "way", + "tags": {"name": "Bory Tucholskie"}, + "geometry": [ + {"lon": 2, "lat": 2}, + {"lon": 3, "lat": 2}, + {"lon": 3, "lat": 3}, + {"lon": 2, "lat": 3}, + ], + }, + # No forest keyword -> skip + { + "type": "way", + "tags": {"name": "Random Wood"}, + "geometry": [ + {"lon": 0, "lat": 0}, + {"lon": 1, "lat": 0}, + {"lon": 1, "lat": 1}, + {"lon": 0, "lat": 1}, + ], + }, + # Duplicate + { + "type": "way", + "tags": {"name": "Puszcza Białowieska"}, + "geometry": [ + {"lon": 0, "lat": 0}, + {"lon": 1, "lat": 0}, + {"lon": 1, "lat": 1}, + {"lon": 0, "lat": 1}, + ], + }, + # No name + {"type": "way", "tags": {}, "geometry": []}, + # Geometry extraction fails (too few coords) + { + "type": "way", + "tags": {"name": "Las Mały"}, + "geometry": [{"lon": 0, "lat": 0}], + }, + ] + } - mock_gdf = gpd.GeoDataFrame( - {"name": ["Puszcza Białowieska", "Bory Tucholskie"]}, - geometry=[_POLY, _POLY], - crs="EPSG:4326", - ) - mock_from_features.return_value = mock_gdf - gdf_with_area = mock_gdf.copy() - gdf_with_area["area_km2"] = [600.0, 300.0] - mock_add_area.return_value = gdf_with_area + mock_gdf = gpd.GeoDataFrame( + {"name": ["Puszcza Białowieska", "Bory Tucholskie"]}, + geometry=[_POLY, _POLY], + crs="EPSG:4326", + ) + mock_from_features.return_value = mock_gdf + gdf_with_area = mock_gdf.copy() + gdf_with_area["area_km2"] = [600.0, 300.0] + mock_add_area.return_value = gdf_with_area - result = get_polish_forests() - assert len(result) == 2 + result = get_polish_forests() + assert len(result) == 2 - @patch("python_pkg.geo_data._poland_nature._add_area_column") - @patch("python_pkg.geo_data._poland_nature.gpd.GeoDataFrame.from_features") - @patch("python_pkg.geo_data._poland_nature._ensure_cache_dir") - @patch("python_pkg.geo_data._poland_nature._overpass_query") - @patch("python_pkg.geo_data._poland_nature.CACHE_DIR") - @patch("python_pkg.geo_data._poland_nature.sys.stdout") - def test_downloads_forests_empty( - self, - mock_stdout: MagicMock, - mock_cache_dir: MagicMock, - mock_query: MagicMock, - mock_ensure: MagicMock, - mock_from_features: MagicMock, - mock_add_area: MagicMock, - ) -> None: - mock_path = MagicMock() - mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) - mock_path.exists.return_value = False - mock_query.return_value = {"elements": []} - empty_gdf = gpd.GeoDataFrame({"name": [], "geometry": []}) - mock_from_features.return_value = empty_gdf - mock_add_area.return_value = empty_gdf - result = get_polish_forests() - assert len(result) == 0 + def test_downloads_forests_empty(self) -> None: + with ( + patch("python_pkg.geo_data._poland_nature.sys.stdout"), + patch("python_pkg.geo_data._poland_nature.CACHE_DIR") as mock_cache_dir, + patch("python_pkg.geo_data._poland_nature._overpass_query") as mock_query, + patch("python_pkg.geo_data._poland_nature._ensure_cache_dir"), + patch( + "python_pkg.geo_data._poland_nature.gpd.GeoDataFrame.from_features" + ) as mock_from_features, + patch( + "python_pkg.geo_data._poland_nature._add_area_column" + ) as mock_add_area, + ): + mock_path = MagicMock() + mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) + mock_path.exists.return_value = False + mock_query.return_value = {"elements": []} + empty_gdf = gpd.GeoDataFrame({"name": [], "geometry": []}) + mock_from_features.return_value = empty_gdf + mock_add_area.return_value = empty_gdf + result = get_polish_forests() + assert len(result) == 0 class TestGetPolishNatureReserves: @@ -225,96 +221,92 @@ class TestGetPolishNatureReserves: result = get_polish_nature_reserves() assert len(result) == 1 - @patch("python_pkg.geo_data._poland_nature._add_area_column") - @patch("python_pkg.geo_data._poland_nature.gpd.GeoDataFrame.from_features") - @patch("python_pkg.geo_data._poland_nature._ensure_cache_dir") - @patch("python_pkg.geo_data._poland_nature._overpass_query") - @patch("python_pkg.geo_data._poland_nature.CACHE_DIR") - @patch("python_pkg.geo_data._poland_nature.sys.stdout") - def test_downloads_reserves( - self, - mock_stdout: MagicMock, - mock_cache_dir: MagicMock, - mock_query: MagicMock, - mock_ensure: MagicMock, - mock_from_features: MagicMock, - mock_add_area: MagicMock, - ) -> None: - mock_path = MagicMock() - mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) - mock_path.exists.return_value = False + def test_downloads_reserves(self) -> None: + with ( + patch("python_pkg.geo_data._poland_nature.sys.stdout"), + patch("python_pkg.geo_data._poland_nature.CACHE_DIR") as mock_cache_dir, + patch("python_pkg.geo_data._poland_nature._overpass_query") as mock_query, + patch("python_pkg.geo_data._poland_nature._ensure_cache_dir"), + patch( + "python_pkg.geo_data._poland_nature.gpd.GeoDataFrame.from_features" + ) as mock_from_features, + patch( + "python_pkg.geo_data._poland_nature._add_area_column" + ) as mock_add_area, + ): + mock_path = MagicMock() + mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) + mock_path.exists.return_value = False - mock_query.return_value = { - "elements": [ - { - "type": "way", - "tags": {"name": "Rezerwat A"}, - "geometry": [ - {"lon": 0, "lat": 0}, - {"lon": 1, "lat": 0}, - {"lon": 1, "lat": 1}, - {"lon": 0, "lat": 1}, - ], - }, - # Duplicate - { - "type": "way", - "tags": {"name": "Rezerwat A"}, - "geometry": [ - {"lon": 0, "lat": 0}, - {"lon": 1, "lat": 0}, - {"lon": 1, "lat": 1}, - {"lon": 0, "lat": 1}, - ], - }, - # No name - {"type": "way", "tags": {}, "geometry": []}, - # Geometry fails - { - "type": "way", - "tags": {"name": "Tiny"}, - "geometry": [{"lon": 0, "lat": 0}], - }, - ] - } + mock_query.return_value = { + "elements": [ + { + "type": "way", + "tags": {"name": "Rezerwat A"}, + "geometry": [ + {"lon": 0, "lat": 0}, + {"lon": 1, "lat": 0}, + {"lon": 1, "lat": 1}, + {"lon": 0, "lat": 1}, + ], + }, + # Duplicate + { + "type": "way", + "tags": {"name": "Rezerwat A"}, + "geometry": [ + {"lon": 0, "lat": 0}, + {"lon": 1, "lat": 0}, + {"lon": 1, "lat": 1}, + {"lon": 0, "lat": 1}, + ], + }, + # No name + {"type": "way", "tags": {}, "geometry": []}, + # Geometry fails + { + "type": "way", + "tags": {"name": "Tiny"}, + "geometry": [{"lon": 0, "lat": 0}], + }, + ] + } - mock_gdf = gpd.GeoDataFrame( - {"name": ["Rezerwat A"]}, - geometry=[_POLY], - crs="EPSG:4326", - ) - mock_from_features.return_value = mock_gdf - gdf_with_area = mock_gdf.copy() - gdf_with_area["area_km2"] = [50.0] - mock_add_area.return_value = gdf_with_area + mock_gdf = gpd.GeoDataFrame( + {"name": ["Rezerwat A"]}, + geometry=[_POLY], + crs="EPSG:4326", + ) + mock_from_features.return_value = mock_gdf + gdf_with_area = mock_gdf.copy() + gdf_with_area["area_km2"] = [50.0] + mock_add_area.return_value = gdf_with_area - result = get_polish_nature_reserves() - assert len(result) == 1 + result = get_polish_nature_reserves() + assert len(result) == 1 - @patch("python_pkg.geo_data._poland_nature._add_area_column") - @patch("python_pkg.geo_data._poland_nature.gpd.GeoDataFrame.from_features") - @patch("python_pkg.geo_data._poland_nature._ensure_cache_dir") - @patch("python_pkg.geo_data._poland_nature._overpass_query") - @patch("python_pkg.geo_data._poland_nature.CACHE_DIR") - @patch("python_pkg.geo_data._poland_nature.sys.stdout") - def test_downloads_reserves_empty( - self, - mock_stdout: MagicMock, - mock_cache_dir: MagicMock, - mock_query: MagicMock, - mock_ensure: MagicMock, - mock_from_features: MagicMock, - mock_add_area: MagicMock, - ) -> None: - mock_path = MagicMock() - mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) - mock_path.exists.return_value = False - mock_query.return_value = {"elements": []} - empty_gdf = gpd.GeoDataFrame({"name": [], "geometry": []}) - mock_from_features.return_value = empty_gdf - mock_add_area.return_value = empty_gdf - result = get_polish_nature_reserves() - assert len(result) == 0 + def test_downloads_reserves_empty(self) -> None: + with ( + patch("python_pkg.geo_data._poland_nature.sys.stdout"), + patch("python_pkg.geo_data._poland_nature.CACHE_DIR") as mock_cache_dir, + patch("python_pkg.geo_data._poland_nature._overpass_query") as mock_query, + patch("python_pkg.geo_data._poland_nature._ensure_cache_dir"), + patch( + "python_pkg.geo_data._poland_nature.gpd.GeoDataFrame.from_features" + ) as mock_from_features, + patch( + "python_pkg.geo_data._poland_nature._add_area_column" + ) as mock_add_area, + ): + mock_path = MagicMock() + mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) + mock_path.exists.return_value = False + mock_query.return_value = {"elements": []} + empty_gdf = gpd.GeoDataFrame({"name": [], "geometry": []}) + mock_from_features.return_value = empty_gdf + mock_add_area.return_value = empty_gdf + result = get_polish_nature_reserves() + assert len(result) == 0 class TestGetPolishLandscapeParks: diff --git a/python_pkg/geo_data/tests/test_poland_water.py b/python_pkg/geo_data/tests/test_poland_water.py index 65b00c5..a0516dc 100644 --- a/python_pkg/geo_data/tests/test_poland_water.py +++ b/python_pkg/geo_data/tests/test_poland_water.py @@ -222,99 +222,95 @@ class TestGetPolishLakes: result = get_polish_lakes() assert len(result) == 1 - @patch("python_pkg.geo_data._poland_water._add_area_column") - @patch("python_pkg.geo_data._poland_water.gpd.GeoDataFrame.from_features") - @patch("python_pkg.geo_data._poland_water._ensure_cache_dir") - @patch("python_pkg.geo_data._poland_water._overpass_query") - @patch("python_pkg.geo_data._poland_water.CACHE_DIR") - @patch("python_pkg.geo_data._poland_water.sys.stdout") - def test_downloads_lakes( - self, - mock_stdout: MagicMock, - mock_cache_dir: MagicMock, - mock_query: MagicMock, - mock_ensure: MagicMock, - mock_from_features: MagicMock, - mock_add_area: MagicMock, - ) -> None: - mock_path = MagicMock() - mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) - mock_path.exists.return_value = False + def test_downloads_lakes(self) -> None: + with ( + patch("python_pkg.geo_data._poland_water.sys.stdout"), + patch("python_pkg.geo_data._poland_water.CACHE_DIR") as mock_cache_dir, + patch("python_pkg.geo_data._poland_water._overpass_query") as mock_query, + patch("python_pkg.geo_data._poland_water._ensure_cache_dir"), + patch( + "python_pkg.geo_data._poland_water.gpd.GeoDataFrame.from_features" + ) as mock_from_features, + patch( + "python_pkg.geo_data._poland_water._add_area_column" + ) as mock_add_area, + ): + mock_path = MagicMock() + mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) + mock_path.exists.return_value = False - mock_query.return_value = { - "elements": [ - { - "type": "way", - "tags": {"name": "Śniardwy"}, - "geometry": [ - {"lon": 0, "lat": 0}, - {"lon": 1, "lat": 0}, - {"lon": 1, "lat": 1}, - {"lon": 0, "lat": 1}, - ], - }, - # Duplicate - { - "type": "way", - "tags": {"name": "Śniardwy"}, - "geometry": [ - {"lon": 0, "lat": 0}, - {"lon": 1, "lat": 0}, - {"lon": 1, "lat": 1}, - {"lon": 0, "lat": 1}, - ], - }, - # No name - {"type": "way", "tags": {}, "geometry": []}, - # Geometry extraction fails - { - "type": "way", - "tags": {"name": "Tiny"}, - "geometry": [{"lon": 0, "lat": 0}], - }, - ] - } + mock_query.return_value = { + "elements": [ + { + "type": "way", + "tags": {"name": "Śniardwy"}, + "geometry": [ + {"lon": 0, "lat": 0}, + {"lon": 1, "lat": 0}, + {"lon": 1, "lat": 1}, + {"lon": 0, "lat": 1}, + ], + }, + # Duplicate + { + "type": "way", + "tags": {"name": "Śniardwy"}, + "geometry": [ + {"lon": 0, "lat": 0}, + {"lon": 1, "lat": 0}, + {"lon": 1, "lat": 1}, + {"lon": 0, "lat": 1}, + ], + }, + # No name + {"type": "way", "tags": {}, "geometry": []}, + # Geometry extraction fails + { + "type": "way", + "tags": {"name": "Tiny"}, + "geometry": [{"lon": 0, "lat": 0}], + }, + ] + } - poly = Polygon([(20, 50), (21, 50), (21, 51), (20, 51)]) - mock_gdf = gpd.GeoDataFrame( - {"name": ["Śniardwy"]}, - geometry=[poly], - crs="EPSG:4326", - ) - mock_from_features.return_value = mock_gdf - gdf_with_area = mock_gdf.copy() - gdf_with_area["area_km2"] = [113.0] - mock_add_area.return_value = gdf_with_area + poly = Polygon([(20, 50), (21, 50), (21, 51), (20, 51)]) + mock_gdf = gpd.GeoDataFrame( + {"name": ["Śniardwy"]}, + geometry=[poly], + crs="EPSG:4326", + ) + mock_from_features.return_value = mock_gdf + gdf_with_area = mock_gdf.copy() + gdf_with_area["area_km2"] = [113.0] + mock_add_area.return_value = gdf_with_area - result = get_polish_lakes() - assert len(result) >= 0 + result = get_polish_lakes() + assert len(result) >= 0 - @patch("python_pkg.geo_data._poland_water._add_area_column") - @patch("python_pkg.geo_data._poland_water.gpd.GeoDataFrame.from_features") - @patch("python_pkg.geo_data._poland_water._ensure_cache_dir") - @patch("python_pkg.geo_data._poland_water._overpass_query") - @patch("python_pkg.geo_data._poland_water.CACHE_DIR") - @patch("python_pkg.geo_data._poland_water.sys.stdout") - def test_empty_result( - self, - mock_stdout: MagicMock, - mock_cache_dir: MagicMock, - mock_query: MagicMock, - mock_ensure: MagicMock, - mock_from_features: MagicMock, - mock_add_area: MagicMock, - ) -> None: - mock_path = MagicMock() - mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) - mock_path.exists.return_value = False - mock_query.return_value = {"elements": []} + def test_empty_result(self) -> None: + with ( + patch("python_pkg.geo_data._poland_water.sys.stdout"), + patch("python_pkg.geo_data._poland_water.CACHE_DIR") as mock_cache_dir, + patch("python_pkg.geo_data._poland_water._overpass_query") as mock_query, + patch("python_pkg.geo_data._poland_water._ensure_cache_dir"), + patch( + "python_pkg.geo_data._poland_water.gpd.GeoDataFrame.from_features" + ) as mock_from_features, + patch( + "python_pkg.geo_data._poland_water._add_area_column" + ) as mock_add_area, + ): + mock_path = MagicMock() + mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) + mock_path.exists.return_value = False + mock_query.return_value = {"elements": []} - empty_gdf = gpd.GeoDataFrame({"name": [], "geometry": []}) - mock_from_features.return_value = empty_gdf - mock_add_area.return_value = empty_gdf + empty_gdf = gpd.GeoDataFrame({"name": [], "geometry": []}) + mock_from_features.return_value = empty_gdf + mock_add_area.return_value = empty_gdf - result = get_polish_lakes() - assert len(result) == 0 + result = get_polish_lakes() + assert len(result) == 0 class TestGetPolishRivers: @@ -358,117 +354,113 @@ class TestGetPolishRivers: result = get_polish_rivers() assert len(result) == 1 - @patch("python_pkg.geo_data._poland_water._add_length_column") - @patch("python_pkg.geo_data._poland_water.gpd.GeoDataFrame.from_features") - @patch("python_pkg.geo_data._poland_water._ensure_cache_dir") - @patch("python_pkg.geo_data._poland_water._overpass_query") - @patch("python_pkg.geo_data._poland_water.CACHE_DIR") - @patch("python_pkg.geo_data._poland_water.sys.stdout") - def test_downloads_rivers( - self, - mock_stdout: MagicMock, - mock_cache_dir: MagicMock, - mock_query: MagicMock, - mock_ensure: MagicMock, - mock_from_features: MagicMock, - mock_add_length: MagicMock, - ) -> None: - mock_path = MagicMock() - mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) - mock_path.exists.return_value = False + def test_downloads_rivers(self) -> None: + with ( + patch("python_pkg.geo_data._poland_water.sys.stdout"), + patch("python_pkg.geo_data._poland_water.CACHE_DIR") as mock_cache_dir, + patch("python_pkg.geo_data._poland_water._overpass_query") as mock_query, + patch("python_pkg.geo_data._poland_water._ensure_cache_dir"), + patch( + "python_pkg.geo_data._poland_water.gpd.GeoDataFrame.from_features" + ) as mock_from_features, + patch( + "python_pkg.geo_data._poland_water._add_length_column" + ) as mock_add_length, + ): + mock_path = MagicMock() + mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) + mock_path.exists.return_value = False - mock_query.return_value = { - "elements": [ - # Way with wikidata - { - "type": "way", - "id": 1, - "tags": {"name": "Wisła", "wikidata": "Q54"}, - "geometry": [{"lon": 0, "lat": 0}, {"lon": 1, "lat": 1}], - }, - # Way without wikidata - { - "type": "way", - "id": 2, - "tags": {"name": "Odra"}, - "geometry": [{"lon": 0, "lat": 0}, {"lon": 1, "lat": 1}], - }, - # Relation - { - "type": "relation", - "id": 3, - "tags": {"name": "Bug", "wikidata": "Q55"}, - "members": [ - { - "type": "way", - "geometry": [ - {"lon": 0, "lat": 0}, - {"lon": 1, "lat": 1}, - ], - }, - { - "type": "way", - "geometry": [ - {"lon": 1, "lat": 1}, - {"lon": 2, "lat": 2}, - ], - }, - ], - }, - # No name - { - "type": "way", - "id": 4, - "tags": {}, - "geometry": [{"lon": 0, "lat": 0}, {"lon": 1, "lat": 1}], - }, - # Way with no coords - { - "type": "way", - "id": 5, - "tags": {"name": "Short"}, - "geometry": [{"lon": 0, "lat": 0}], - }, - ] - } + mock_query.return_value = { + "elements": [ + # Way with wikidata + { + "type": "way", + "id": 1, + "tags": {"name": "Wisła", "wikidata": "Q54"}, + "geometry": [{"lon": 0, "lat": 0}, {"lon": 1, "lat": 1}], + }, + # Way without wikidata + { + "type": "way", + "id": 2, + "tags": {"name": "Odra"}, + "geometry": [{"lon": 0, "lat": 0}, {"lon": 1, "lat": 1}], + }, + # Relation + { + "type": "relation", + "id": 3, + "tags": {"name": "Bug", "wikidata": "Q55"}, + "members": [ + { + "type": "way", + "geometry": [ + {"lon": 0, "lat": 0}, + {"lon": 1, "lat": 1}, + ], + }, + { + "type": "way", + "geometry": [ + {"lon": 1, "lat": 1}, + {"lon": 2, "lat": 2}, + ], + }, + ], + }, + # No name + { + "type": "way", + "id": 4, + "tags": {}, + "geometry": [{"lon": 0, "lat": 0}, {"lon": 1, "lat": 1}], + }, + # Way with no coords + { + "type": "way", + "id": 5, + "tags": {"name": "Short"}, + "geometry": [{"lon": 0, "lat": 0}], + }, + ] + } - poly = Polygon([(20, 50), (21, 50), (21, 51), (20, 51)]) - mock_gdf = gpd.GeoDataFrame( - {"name": ["Wisła", "Odra", "Bug"]}, - geometry=[poly, poly, poly], - crs="EPSG:4326", - ) - mock_from_features.return_value = mock_gdf - gdf_with_length = mock_gdf.copy() - gdf_with_length["length_km"] = [1047.0, 854.0, 772.0] - mock_add_length.return_value = gdf_with_length + poly = Polygon([(20, 50), (21, 50), (21, 51), (20, 51)]) + mock_gdf = gpd.GeoDataFrame( + {"name": ["Wisła", "Odra", "Bug"]}, + geometry=[poly, poly, poly], + crs="EPSG:4326", + ) + mock_from_features.return_value = mock_gdf + gdf_with_length = mock_gdf.copy() + gdf_with_length["length_km"] = [1047.0, 854.0, 772.0] + mock_add_length.return_value = gdf_with_length - result = get_polish_rivers() - assert len(result) >= 0 + result = get_polish_rivers() + assert len(result) >= 0 - @patch("python_pkg.geo_data._poland_water._add_length_column") - @patch("python_pkg.geo_data._poland_water.gpd.GeoDataFrame.from_features") - @patch("python_pkg.geo_data._poland_water._ensure_cache_dir") - @patch("python_pkg.geo_data._poland_water._overpass_query") - @patch("python_pkg.geo_data._poland_water.CACHE_DIR") - @patch("python_pkg.geo_data._poland_water.sys.stdout") - def test_empty_result( - self, - mock_stdout: MagicMock, - mock_cache_dir: MagicMock, - mock_query: MagicMock, - mock_ensure: MagicMock, - mock_from_features: MagicMock, - mock_add_length: MagicMock, - ) -> None: - mock_path = MagicMock() - mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) - mock_path.exists.return_value = False - mock_query.return_value = {"elements": []} + def test_empty_result(self) -> None: + with ( + patch("python_pkg.geo_data._poland_water.sys.stdout"), + patch("python_pkg.geo_data._poland_water.CACHE_DIR") as mock_cache_dir, + patch("python_pkg.geo_data._poland_water._overpass_query") as mock_query, + patch("python_pkg.geo_data._poland_water._ensure_cache_dir"), + patch( + "python_pkg.geo_data._poland_water.gpd.GeoDataFrame.from_features" + ) as mock_from_features, + patch( + "python_pkg.geo_data._poland_water._add_length_column" + ) as mock_add_length, + ): + mock_path = MagicMock() + mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) + mock_path.exists.return_value = False + mock_query.return_value = {"elements": []} - empty_gdf = gpd.GeoDataFrame({"name": [], "geometry": []}) - mock_from_features.return_value = empty_gdf - mock_add_length.return_value = empty_gdf + empty_gdf = gpd.GeoDataFrame({"name": [], "geometry": []}) + mock_from_features.return_value = empty_gdf + mock_add_length.return_value = empty_gdf - result = get_polish_rivers() - assert len(result) == 0 + result = get_polish_rivers() + assert len(result) == 0 diff --git a/python_pkg/geo_data/tests/test_poland_water_part2.py b/python_pkg/geo_data/tests/test_poland_water_part2.py index 528d083..92fa4c4 100644 --- a/python_pkg/geo_data/tests/test_poland_water_part2.py +++ b/python_pkg/geo_data/tests/test_poland_water_part2.py @@ -73,96 +73,92 @@ class TestGetPolishIslands: result = get_polish_islands() assert len(result) == 1 - @patch("python_pkg.geo_data._poland_water._add_area_column") - @patch("python_pkg.geo_data._poland_water.gpd.GeoDataFrame.from_features") - @patch("python_pkg.geo_data._poland_water._ensure_cache_dir") - @patch("python_pkg.geo_data._poland_water._overpass_query") - @patch("python_pkg.geo_data._poland_water.CACHE_DIR") - @patch("python_pkg.geo_data._poland_water.sys.stdout") - def test_downloads_islands( - self, - mock_stdout: MagicMock, - mock_cache_dir: MagicMock, - mock_query: MagicMock, - mock_ensure: MagicMock, - mock_from_features: MagicMock, - mock_add_area: MagicMock, - ) -> None: - mock_path = MagicMock() - mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) - mock_path.exists.return_value = False + def test_downloads_islands(self) -> None: + with ( + patch("python_pkg.geo_data._poland_water.sys.stdout"), + patch("python_pkg.geo_data._poland_water.CACHE_DIR") as mock_cache_dir, + patch("python_pkg.geo_data._poland_water._overpass_query") as mock_query, + patch("python_pkg.geo_data._poland_water._ensure_cache_dir"), + patch( + "python_pkg.geo_data._poland_water.gpd.GeoDataFrame.from_features" + ) as mock_from_features, + patch( + "python_pkg.geo_data._poland_water._add_area_column" + ) as mock_add_area, + ): + mock_path = MagicMock() + mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) + mock_path.exists.return_value = False - mock_query.return_value = { - "elements": [ - { - "type": "way", - "tags": {"name": "Wolin"}, - "geometry": [ - {"lon": 0, "lat": 0}, - {"lon": 1, "lat": 0}, - {"lon": 1, "lat": 1}, - {"lon": 0, "lat": 1}, - ], - }, - # Duplicate - { - "type": "way", - "tags": {"name": "Wolin"}, - "geometry": [ - {"lon": 0, "lat": 0}, - {"lon": 1, "lat": 0}, - {"lon": 1, "lat": 1}, - {"lon": 0, "lat": 1}, - ], - }, - # No name - {"type": "way", "tags": {}, "geometry": []}, - # Geometry fails - { - "type": "way", - "tags": {"name": "Tiny"}, - "geometry": [{"lon": 0, "lat": 0}], - }, - ] - } + mock_query.return_value = { + "elements": [ + { + "type": "way", + "tags": {"name": "Wolin"}, + "geometry": [ + {"lon": 0, "lat": 0}, + {"lon": 1, "lat": 0}, + {"lon": 1, "lat": 1}, + {"lon": 0, "lat": 1}, + ], + }, + # Duplicate + { + "type": "way", + "tags": {"name": "Wolin"}, + "geometry": [ + {"lon": 0, "lat": 0}, + {"lon": 1, "lat": 0}, + {"lon": 1, "lat": 1}, + {"lon": 0, "lat": 1}, + ], + }, + # No name + {"type": "way", "tags": {}, "geometry": []}, + # Geometry fails + { + "type": "way", + "tags": {"name": "Tiny"}, + "geometry": [{"lon": 0, "lat": 0}], + }, + ] + } - mock_gdf = gpd.GeoDataFrame( - {"name": ["Wolin"]}, - geometry=[_POLY], - crs="EPSG:4326", - ) - mock_from_features.return_value = mock_gdf - gdf_with_area = mock_gdf.copy() - gdf_with_area["area_km2"] = [265.0] - mock_add_area.return_value = gdf_with_area + mock_gdf = gpd.GeoDataFrame( + {"name": ["Wolin"]}, + geometry=[_POLY], + crs="EPSG:4326", + ) + mock_from_features.return_value = mock_gdf + gdf_with_area = mock_gdf.copy() + gdf_with_area["area_km2"] = [265.0] + mock_add_area.return_value = gdf_with_area - result = get_polish_islands() - assert len(result) == 1 + result = get_polish_islands() + assert len(result) == 1 - @patch("python_pkg.geo_data._poland_water._add_area_column") - @patch("python_pkg.geo_data._poland_water.gpd.GeoDataFrame.from_features") - @patch("python_pkg.geo_data._poland_water._ensure_cache_dir") - @patch("python_pkg.geo_data._poland_water._overpass_query") - @patch("python_pkg.geo_data._poland_water.CACHE_DIR") - @patch("python_pkg.geo_data._poland_water.sys.stdout") - def test_downloads_islands_empty( - self, - mock_stdout: MagicMock, - mock_cache_dir: MagicMock, - mock_query: MagicMock, - mock_ensure: MagicMock, - mock_from_features: MagicMock, - mock_add_area: MagicMock, - ) -> None: - mock_path = MagicMock() - mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) - mock_path.exists.return_value = False - mock_query.return_value = {"elements": []} - empty_gdf = gpd.GeoDataFrame({"name": [], "geometry": []}) - mock_from_features.return_value = empty_gdf - mock_add_area.return_value = empty_gdf - result = get_polish_islands() - assert len(result) == 0 + def test_downloads_islands_empty(self) -> None: + with ( + patch("python_pkg.geo_data._poland_water.sys.stdout"), + patch("python_pkg.geo_data._poland_water.CACHE_DIR") as mock_cache_dir, + patch("python_pkg.geo_data._poland_water._overpass_query") as mock_query, + patch("python_pkg.geo_data._poland_water._ensure_cache_dir"), + patch( + "python_pkg.geo_data._poland_water.gpd.GeoDataFrame.from_features" + ) as mock_from_features, + patch( + "python_pkg.geo_data._poland_water._add_area_column" + ) as mock_add_area, + ): + mock_path = MagicMock() + mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) + mock_path.exists.return_value = False + mock_query.return_value = {"elements": []} + empty_gdf = gpd.GeoDataFrame({"name": [], "geometry": []}) + mock_from_features.return_value = empty_gdf + mock_add_area.return_value = empty_gdf + result = get_polish_islands() + assert len(result) == 0 class TestGetPolishCoastalFeatures: @@ -202,109 +198,105 @@ class TestGetPolishCoastalFeatures: result = get_polish_coastal_features() assert len(result) == 1 - @patch("python_pkg.geo_data._poland_water._add_length_column") - @patch("python_pkg.geo_data._poland_water.gpd.GeoDataFrame.from_features") - @patch("python_pkg.geo_data._poland_water._ensure_cache_dir") - @patch("python_pkg.geo_data._poland_water._overpass_query") - @patch("python_pkg.geo_data._poland_water.CACHE_DIR") - @patch("python_pkg.geo_data._poland_water.sys.stdout") - def test_downloads_coastal_features( - self, - mock_stdout: MagicMock, - mock_cache_dir: MagicMock, - mock_query: MagicMock, - mock_ensure: MagicMock, - mock_from_features: MagicMock, - mock_add_length: MagicMock, - ) -> None: - mock_path = MagicMock() - mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) - mock_path.exists.return_value = False + def test_downloads_coastal_features(self) -> None: + with ( + patch("python_pkg.geo_data._poland_water.sys.stdout"), + patch("python_pkg.geo_data._poland_water.CACHE_DIR") as mock_cache_dir, + patch("python_pkg.geo_data._poland_water._overpass_query") as mock_query, + patch("python_pkg.geo_data._poland_water._ensure_cache_dir"), + patch( + "python_pkg.geo_data._poland_water.gpd.GeoDataFrame.from_features" + ) as mock_from_features, + patch( + "python_pkg.geo_data._poland_water._add_length_column" + ) as mock_add_length, + ): + mock_path = MagicMock() + mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) + mock_path.exists.return_value = False - mock_query.return_value = { - "elements": [ - # Peninsula (polygon type) - { - "type": "way", - "tags": {"name": "Hel", "natural": "peninsula"}, - "geometry": [ - {"lon": 0, "lat": 0}, - {"lon": 1, "lat": 0}, - {"lon": 1, "lat": 1}, - {"lon": 0, "lat": 1}, - ], - }, - # Cliff (line type) - { - "type": "way", - "tags": {"name": "Klif Orłowski", "natural": "cliff"}, - "geometry": [ - {"lon": 0, "lat": 0}, - {"lon": 1, "lat": 1}, - ], - }, - # Duplicate - { - "type": "way", - "tags": {"name": "Hel", "natural": "peninsula"}, - "geometry": [ - {"lon": 0, "lat": 0}, - {"lon": 1, "lat": 0}, - {"lon": 1, "lat": 1}, - {"lon": 0, "lat": 1}, - ], - }, - # No name - { - "type": "way", - "tags": {"natural": "cliff"}, - "geometry": [], - }, - # Geometry fails (no geometry key) - { - "type": "node", - "tags": {"name": "X", "natural": "cliff"}, - }, - ] - } + mock_query.return_value = { + "elements": [ + # Peninsula (polygon type) + { + "type": "way", + "tags": {"name": "Hel", "natural": "peninsula"}, + "geometry": [ + {"lon": 0, "lat": 0}, + {"lon": 1, "lat": 0}, + {"lon": 1, "lat": 1}, + {"lon": 0, "lat": 1}, + ], + }, + # Cliff (line type) + { + "type": "way", + "tags": {"name": "Klif Orłowski", "natural": "cliff"}, + "geometry": [ + {"lon": 0, "lat": 0}, + {"lon": 1, "lat": 1}, + ], + }, + # Duplicate + { + "type": "way", + "tags": {"name": "Hel", "natural": "peninsula"}, + "geometry": [ + {"lon": 0, "lat": 0}, + {"lon": 1, "lat": 0}, + {"lon": 1, "lat": 1}, + {"lon": 0, "lat": 1}, + ], + }, + # No name + { + "type": "way", + "tags": {"natural": "cliff"}, + "geometry": [], + }, + # Geometry fails (no geometry key) + { + "type": "node", + "tags": {"name": "X", "natural": "cliff"}, + }, + ] + } - mock_gdf = gpd.GeoDataFrame( - {"name": ["Hel", "Klif Orłowski"]}, - geometry=[_POLY, _POLY], - crs="EPSG:4326", - ) - mock_from_features.return_value = mock_gdf - gdf_with_length = mock_gdf.copy() - gdf_with_length["length_km"] = [35.0, 5.0] - mock_add_length.return_value = gdf_with_length + mock_gdf = gpd.GeoDataFrame( + {"name": ["Hel", "Klif Orłowski"]}, + geometry=[_POLY, _POLY], + crs="EPSG:4326", + ) + mock_from_features.return_value = mock_gdf + gdf_with_length = mock_gdf.copy() + gdf_with_length["length_km"] = [35.0, 5.0] + mock_add_length.return_value = gdf_with_length - result = get_polish_coastal_features() - assert len(result) == 2 + result = get_polish_coastal_features() + assert len(result) == 2 - @patch("python_pkg.geo_data._poland_water._add_length_column") - @patch("python_pkg.geo_data._poland_water.gpd.GeoDataFrame.from_features") - @patch("python_pkg.geo_data._poland_water._ensure_cache_dir") - @patch("python_pkg.geo_data._poland_water._overpass_query") - @patch("python_pkg.geo_data._poland_water.CACHE_DIR") - @patch("python_pkg.geo_data._poland_water.sys.stdout") - def test_downloads_coastal_features_empty( - self, - mock_stdout: MagicMock, - mock_cache_dir: MagicMock, - mock_query: MagicMock, - mock_ensure: MagicMock, - mock_from_features: MagicMock, - mock_add_length: MagicMock, - ) -> None: - mock_path = MagicMock() - mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) - mock_path.exists.return_value = False - mock_query.return_value = {"elements": []} - empty_gdf = gpd.GeoDataFrame({"name": [], "geometry": []}) - mock_from_features.return_value = empty_gdf - mock_add_length.return_value = empty_gdf - result = get_polish_coastal_features() - assert len(result) == 0 + def test_downloads_coastal_features_empty(self) -> None: + with ( + patch("python_pkg.geo_data._poland_water.sys.stdout"), + patch("python_pkg.geo_data._poland_water.CACHE_DIR") as mock_cache_dir, + patch("python_pkg.geo_data._poland_water._overpass_query") as mock_query, + patch("python_pkg.geo_data._poland_water._ensure_cache_dir"), + patch( + "python_pkg.geo_data._poland_water.gpd.GeoDataFrame.from_features" + ) as mock_from_features, + patch( + "python_pkg.geo_data._poland_water._add_length_column" + ) as mock_add_length, + ): + mock_path = MagicMock() + mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) + mock_path.exists.return_value = False + mock_query.return_value = {"elements": []} + empty_gdf = gpd.GeoDataFrame({"name": [], "geometry": []}) + mock_from_features.return_value = empty_gdf + mock_add_length.return_value = empty_gdf + result = get_polish_coastal_features() + assert len(result) == 0 class TestGetPolishUnescoSites: diff --git a/python_pkg/geo_data/tests/test_warsaw.py b/python_pkg/geo_data/tests/test_warsaw.py index 482670c..2a58cb3 100644 --- a/python_pkg/geo_data/tests/test_warsaw.py +++ b/python_pkg/geo_data/tests/test_warsaw.py @@ -114,74 +114,71 @@ class TestGetWarsawBoundary: result = get_warsaw_boundary() assert len(result) == 1 - @patch("python_pkg.geo_data._warsaw.gpd.GeoDataFrame.from_features") - @patch("python_pkg.geo_data._warsaw._ensure_cache_dir") - @patch("python_pkg.geo_data._warsaw._overpass_query") - @patch("python_pkg.geo_data._warsaw._PKG_DIR") - @patch("python_pkg.geo_data._warsaw.CACHE_DIR") - @patch("python_pkg.geo_data._warsaw.sys.stdout") - def test_fallback_overpass( - self, - mock_stdout: MagicMock, - mock_cache_dir: MagicMock, - mock_pkg_dir: MagicMock, - mock_query: MagicMock, - mock_ensure: MagicMock, - mock_from_features: MagicMock, - ) -> None: - mock_cache_path = MagicMock() - mock_cache_dir.__truediv__ = MagicMock(return_value=mock_cache_path) - mock_cache_path.exists.return_value = False + def test_fallback_overpass(self) -> None: + with ( + patch("python_pkg.geo_data._warsaw.sys.stdout"), + patch("python_pkg.geo_data._warsaw.CACHE_DIR") as mock_cache_dir, + patch("python_pkg.geo_data._warsaw._PKG_DIR") as mock_pkg_dir, + patch("python_pkg.geo_data._warsaw._overpass_query") as mock_query, + patch("python_pkg.geo_data._warsaw._ensure_cache_dir"), + patch( + "python_pkg.geo_data._warsaw.gpd.GeoDataFrame.from_features" + ) as mock_from_features, + ): + mock_cache_path = MagicMock() + mock_cache_dir.__truediv__ = MagicMock(return_value=mock_cache_path) + mock_cache_path.exists.return_value = False - mock_districts_path = MagicMock() - mock_pkg_dir.__truediv__ = MagicMock(return_value=MagicMock()) - mock_pkg_dir.__truediv__.return_value.__truediv__ = MagicMock( - return_value=MagicMock() - ) - mock_pkg_dir.__truediv__.return_value.__truediv__.return_value.__truediv__ = ( - MagicMock(return_value=mock_districts_path) - ) - mock_districts_path.exists.return_value = False + mock_districts_path = MagicMock() + mock_pkg_dir.__truediv__ = MagicMock(return_value=MagicMock()) + mock_pkg_dir.__truediv__.return_value.__truediv__ = MagicMock( + return_value=MagicMock() + ) + nested = mock_pkg_dir.__truediv__.return_value.__truediv__ + nested.return_value.__truediv__ = MagicMock( + return_value=mock_districts_path + ) + mock_districts_path.exists.return_value = False - mock_query.return_value = { - "elements": [ - { - "type": "relation", - "members": [ - { - "role": "outer", - "geometry": [ - {"lon": 20, "lat": 52}, - {"lon": 21, "lat": 52}, - {"lon": 21, "lat": 53}, - ], - }, - # non-outer member - { - "role": "inner", - "geometry": [ - {"lon": 20.5, "lat": 52.5}, - ], - }, - ], - }, - # Not a relation - {"type": "way"}, - # Relation with no outer geometry (empty coords) - { - "type": "relation", - "members": [ - {"role": "inner", "geometry": [{"lon": 20, "lat": 52}]}, - ], - }, - ] - } + mock_query.return_value = { + "elements": [ + { + "type": "relation", + "members": [ + { + "role": "outer", + "geometry": [ + {"lon": 20, "lat": 52}, + {"lon": 21, "lat": 52}, + {"lon": 21, "lat": 53}, + ], + }, + # non-outer member + { + "role": "inner", + "geometry": [ + {"lon": 20.5, "lat": 52.5}, + ], + }, + ], + }, + # Not a relation + {"type": "way"}, + # Relation with no outer geometry (empty coords) + { + "type": "relation", + "members": [ + {"role": "inner", "geometry": [{"lon": 20, "lat": 52}]}, + ], + }, + ] + } - mock_gdf = MagicMock(spec=gpd.GeoDataFrame) - mock_from_features.return_value = mock_gdf + mock_gdf = MagicMock(spec=gpd.GeoDataFrame) + mock_from_features.return_value = mock_gdf - result = get_warsaw_boundary() - assert result is mock_gdf + result = get_warsaw_boundary() + assert result is mock_gdf class TestGetWarsawDistricts: @@ -311,94 +308,90 @@ class TestGetWarsawBridges: result = get_warsaw_bridges() assert result is mock_gdf - @patch("python_pkg.geo_data._warsaw.gpd.GeoDataFrame.from_features") - @patch("python_pkg.geo_data._warsaw._ensure_cache_dir") - @patch("python_pkg.geo_data._warsaw._overpass_query") - @patch("python_pkg.geo_data._warsaw.get_vistula_river") - @patch("python_pkg.geo_data._warsaw.CACHE_DIR") - @patch("python_pkg.geo_data._warsaw.sys.stdout") - def test_downloads( - self, - mock_stdout: MagicMock, - mock_cache_dir: MagicMock, - mock_vistula: MagicMock, - mock_query: MagicMock, - mock_ensure: MagicMock, - mock_from_features: MagicMock, - ) -> None: - mock_path = MagicMock() - mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) - mock_path.exists.return_value = False + def test_downloads(self) -> None: + with ( + patch("python_pkg.geo_data._warsaw.sys.stdout"), + patch("python_pkg.geo_data._warsaw.CACHE_DIR") as mock_cache_dir, + patch("python_pkg.geo_data._warsaw.get_vistula_river") as mock_vistula, + patch("python_pkg.geo_data._warsaw._overpass_query") as mock_query, + patch("python_pkg.geo_data._warsaw._ensure_cache_dir"), + patch( + "python_pkg.geo_data._warsaw.gpd.GeoDataFrame.from_features" + ) as mock_from_features, + ): + mock_path = MagicMock() + mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) + mock_path.exists.return_value = False - # Create a real Vistula geometry for intersection tests - vistula_gdf = gpd.GeoDataFrame( - {"name": ["Wisła"]}, - geometry=[LineString([(20.0, 52.2), (21.0, 52.2)])], - crs="EPSG:4326", - ) - mock_vistula.return_value = vistula_gdf + # Create a real Vistula geometry for intersection tests + vistula_gdf = gpd.GeoDataFrame( + {"name": ["Wisła"]}, + geometry=[LineString([(20.0, 52.2), (21.0, 52.2)])], + crs="EPSG:4326", + ) + mock_vistula.return_value = vistula_gdf - mock_query.return_value = { - "elements": [ - # Bridge that intersects vistula buffer - { - "type": "way", - "id": 1, - "tags": {"name": "Most Łazienkowski"}, - "geometry": [ - {"lon": 20.5, "lat": 52.19}, - {"lon": 20.5, "lat": 52.21}, - ], - }, - # Bridge far from vistula - { - "type": "way", - "id": 2, - "tags": {"name": "Most Daleki"}, - "geometry": [ - {"lon": 20.5, "lat": 55.0}, - {"lon": 20.5, "lat": 55.1}, - ], - }, - # Not a way - {"type": "node", "tags": {"name": "Most X"}}, - # Way without geometry - {"type": "way", "tags": {"name": "Most Y"}}, - # No name - { - "type": "way", - "id": 3, - "tags": {}, - "geometry": [ - {"lon": 20.5, "lat": 52.19}, - {"lon": 20.5, "lat": 52.21}, - ], - }, - # Duplicate - { - "type": "way", - "id": 4, - "tags": {"name": "Most Łazienkowski"}, - "geometry": [ - {"lon": 20.5, "lat": 52.19}, - {"lon": 20.5, "lat": 52.21}, - ], - }, - # Too few coords - { - "type": "way", - "id": 5, - "tags": {"name": "Most Short"}, - "geometry": [{"lon": 20.5, "lat": 52.19}], - }, - ] - } + mock_query.return_value = { + "elements": [ + # Bridge that intersects vistula buffer + { + "type": "way", + "id": 1, + "tags": {"name": "Most Łazienkowski"}, + "geometry": [ + {"lon": 20.5, "lat": 52.19}, + {"lon": 20.5, "lat": 52.21}, + ], + }, + # Bridge far from vistula + { + "type": "way", + "id": 2, + "tags": {"name": "Most Daleki"}, + "geometry": [ + {"lon": 20.5, "lat": 55.0}, + {"lon": 20.5, "lat": 55.1}, + ], + }, + # Not a way + {"type": "node", "tags": {"name": "Most X"}}, + # Way without geometry + {"type": "way", "tags": {"name": "Most Y"}}, + # No name + { + "type": "way", + "id": 3, + "tags": {}, + "geometry": [ + {"lon": 20.5, "lat": 52.19}, + {"lon": 20.5, "lat": 52.21}, + ], + }, + # Duplicate + { + "type": "way", + "id": 4, + "tags": {"name": "Most Łazienkowski"}, + "geometry": [ + {"lon": 20.5, "lat": 52.19}, + {"lon": 20.5, "lat": 52.21}, + ], + }, + # Too few coords + { + "type": "way", + "id": 5, + "tags": {"name": "Most Short"}, + "geometry": [{"lon": 20.5, "lat": 52.19}], + }, + ] + } - mock_gdf = MagicMock(spec=gpd.GeoDataFrame) - mock_from_features.return_value = mock_gdf + mock_gdf = MagicMock(spec=gpd.GeoDataFrame) + mock_from_features.return_value = mock_gdf - result = get_warsaw_bridges() - assert result is mock_gdf + result = get_warsaw_bridges() + assert result is mock_gdf class TestMergeBridgeSegments: diff --git a/python_pkg/geo_data/tests/test_warsaw_places.py b/python_pkg/geo_data/tests/test_warsaw_places.py index f102e6d..40c526c 100644 --- a/python_pkg/geo_data/tests/test_warsaw_places.py +++ b/python_pkg/geo_data/tests/test_warsaw_places.py @@ -37,54 +37,52 @@ class TestGetWarsawStreets: result = get_warsaw_streets() assert result is mock_gdf - @patch("python_pkg.geo_data._warsaw_places._filter_streets_by_length") - @patch("python_pkg.geo_data._warsaw_places.gpd.GeoDataFrame.from_features") - @patch("python_pkg.geo_data._warsaw_places._ensure_cache_dir") - @patch("python_pkg.geo_data._warsaw_places._overpass_query") - @patch("python_pkg.geo_data._warsaw_places.CACHE_DIR") - @patch("python_pkg.geo_data._warsaw_places.sys.stdout") - def test_downloads( - self, - mock_stdout: MagicMock, - mock_cache_dir: MagicMock, - mock_query: MagicMock, - mock_ensure: MagicMock, - mock_from_features: MagicMock, - mock_filter: MagicMock, - ) -> None: - mock_path = MagicMock() - mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) - mock_path.exists.return_value = False + def test_downloads(self) -> None: + with ( + patch("python_pkg.geo_data._warsaw_places.sys.stdout"), + patch("python_pkg.geo_data._warsaw_places.CACHE_DIR") as mock_cache_dir, + patch("python_pkg.geo_data._warsaw_places._overpass_query") as mock_query, + patch("python_pkg.geo_data._warsaw_places._ensure_cache_dir"), + patch( + "python_pkg.geo_data._warsaw_places.gpd.GeoDataFrame.from_features" + ) as mock_from_features, + patch( + "python_pkg.geo_data._warsaw_places._filter_streets_by_length" + ) as mock_filter, + ): + mock_path = MagicMock() + mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) + mock_path.exists.return_value = False - mock_query.return_value = { - "elements": [ - { - "type": "way", - "tags": {"name": "Marszałkowska", "highway": "primary"}, - "geometry": [ - {"lon": 21.0, "lat": 52.2}, - {"lon": 21.0, "lat": 52.3}, - ], - }, - # Too few coords - { - "type": "way", - "tags": {"name": "Short"}, - "geometry": [{"lon": 21.0, "lat": 52.2}], - }, - # Not a way - {"type": "node", "tags": {"name": "Node"}}, - # Way without geometry - {"type": "way", "tags": {"name": "NoGeom"}}, - ] - } + mock_query.return_value = { + "elements": [ + { + "type": "way", + "tags": {"name": "Marszałkowska", "highway": "primary"}, + "geometry": [ + {"lon": 21.0, "lat": 52.2}, + {"lon": 21.0, "lat": 52.3}, + ], + }, + # Too few coords + { + "type": "way", + "tags": {"name": "Short"}, + "geometry": [{"lon": 21.0, "lat": 52.2}], + }, + # Not a way + {"type": "node", "tags": {"name": "Node"}}, + # Way without geometry + {"type": "way", "tags": {"name": "NoGeom"}}, + ] + } - mock_gdf = MagicMock(spec=gpd.GeoDataFrame) - mock_from_features.return_value = mock_gdf - mock_filter.return_value = mock_gdf + mock_gdf = MagicMock(spec=gpd.GeoDataFrame) + mock_from_features.return_value = mock_gdf + mock_filter.return_value = mock_gdf - result = get_warsaw_streets() - assert result is mock_gdf + result = get_warsaw_streets() + assert result is mock_gdf class TestFilterStreetsByLength: diff --git a/python_pkg/keyboard_coop/main.py b/python_pkg/keyboard_coop/main.py index 55f73ef..05051b7 100644 --- a/python_pkg/keyboard_coop/main.py +++ b/python_pkg/keyboard_coop/main.py @@ -297,7 +297,9 @@ class KeyboardCoopGame: pygame.draw.rect(self.screen, TEXT_COLOR, rect, 2) # Draw letter - text = self.fonts.small.render(letter.upper(), True, TEXT_COLOR) + text = self.fonts.small.render( + letter.upper(), antialias=True, color=TEXT_COLOR + ) text_rect = text.get_rect(center=rect.center) self.screen.blit(text, text_rect) @@ -305,14 +307,14 @@ class KeyboardCoopGame: self, text: str, pos: tuple[int, int], font: pygame.font.Font ) -> None: """Draw a single line of text at the given position.""" - rendered = font.render(text, True, TEXT_COLOR) + rendered = font.render(text, antialias=True, color=TEXT_COLOR) self.screen.blit(rendered, pos) def _draw_button(self, rect: pygame.Rect, label: str) -> None: """Draw a button with the given label.""" pygame.draw.rect(self.screen, KEY_COLOR, rect) pygame.draw.rect(self.screen, TEXT_COLOR, rect, 2) - text = self.fonts.small.render(label, True, TEXT_COLOR) + text = self.fonts.small.render(label, antialias=True, color=TEXT_COLOR) self.screen.blit(text, text.get_rect(center=rect.center)) def _draw_ui(self) -> tuple[pygame.Rect, pygame.Rect]: @@ -329,7 +331,9 @@ class KeyboardCoopGame: # Current player with color player_color = PLAYER_COLORS[self.state.current_player] player_text = self.fonts.normal.render( - f"Current Player: {self.state.current_player + 1}", True, player_color + f"Current Player: {self.state.current_player + 1}", + antialias=True, + color=player_color, ) self.screen.blit(player_text, (30, 100)) diff --git a/python_pkg/moviepy_showcase/moviepy_showcase.py b/python_pkg/moviepy_showcase/moviepy_showcase.py index 58163cd..1541f63 100644 --- a/python_pkg/moviepy_showcase/moviepy_showcase.py +++ b/python_pkg/moviepy_showcase/moviepy_showcase.py @@ -194,7 +194,9 @@ def main() -> None: def _build(tmpdir: str) -> None: - # ── Lazy imports of moved part builders ─────────────────────── + # ── Lazy imports to avoid circular dependency ──────────────── + # These submodules import constants from this module, so they + # cannot be imported at the top level. from moviepy.audio.fx import MultiplyVolume from python_pkg.moviepy_showcase._moviepy_audio_output import ( @@ -208,7 +210,9 @@ def _build(tmpdir: str) -> None: part1_clip_types, part2_clip_methods, ) - from python_pkg.moviepy_showcase._moviepy_video_effects import part3_video_effects + from python_pkg.moviepy_showcase._moviepy_video_effects import ( + part3_video_effects, + ) # ── Render each part to its own temp file ───────────────────── # Title card diff --git a/python_pkg/moviepy_showcase/tests/conftest.py b/python_pkg/moviepy_showcase/tests/conftest.py index 6417832..6dc920f 100644 --- a/python_pkg/moviepy_showcase/tests/conftest.py +++ b/python_pkg/moviepy_showcase/tests/conftest.py @@ -16,7 +16,7 @@ import pytest _H, _W = 1080, 1920 -def create_mock_clip(**overrides: Any) -> MagicMock: +def create_mock_clip(**overrides: float | tuple[int, int]) -> MagicMock: """Return a MagicMock that behaves enough like a moviepy clip.""" clip = MagicMock() clip.duration = overrides.get("duration", 2.0) @@ -71,12 +71,12 @@ _clip_classes = [ "CompositeAudioClip", ] for _cls in _clip_classes: - getattr(mock_moviepy, _cls).side_effect = lambda *a, **kw: create_mock_clip() + getattr(mock_moviepy, _cls).side_effect = lambda *_a, **_kw: create_mock_clip() -mock_moviepy.concatenate_videoclips.side_effect = lambda *a, **kw: create_mock_clip() -mock_moviepy.concatenate_audioclips.side_effect = lambda *a, **kw: create_mock_clip() +mock_moviepy.concatenate_videoclips.side_effect = lambda *_a, **_kw: create_mock_clip() +mock_moviepy.concatenate_audioclips.side_effect = lambda *_a, **_kw: create_mock_clip() mock_moviepy.video.compositing.CompositeVideoClip.clips_array.side_effect = ( - lambda *a, **kw: create_mock_clip() + lambda *_a, **_kw: create_mock_clip() ) # Drawing tools must return real numpy arrays (used in numpy ops) diff --git a/python_pkg/moviepy_showcase/tests/test_audio_output.py b/python_pkg/moviepy_showcase/tests/test_audio_output.py index ab6fb1c..6e9e06c 100644 --- a/python_pkg/moviepy_showcase/tests/test_audio_output.py +++ b/python_pkg/moviepy_showcase/tests/test_audio_output.py @@ -25,7 +25,7 @@ def test_make_sine_maker_scalar() -> None: """maker() with scalar t → t_arr.ndim == 0 → returns 1-D.""" import moviepy as mp - mp.AudioClip.side_effect = lambda *a, **kw: MagicMock() + mp.AudioClip.side_effect = lambda *_a, **_kw: MagicMock() _make_sine(440.0, 1.0) maker = mp.AudioClip.call_args[0][0] @@ -39,7 +39,7 @@ def test_make_sine_maker_array() -> None: """maker() with array t → t_arr.ndim > 0 → returns 2-D.""" import moviepy as mp - mp.AudioClip.side_effect = lambda *a, **kw: MagicMock() + mp.AudioClip.side_effect = lambda *_a, **_kw: MagicMock() _make_sine(440.0, 1.0) maker = mp.AudioClip.call_args[0][0] diff --git a/python_pkg/moviepy_showcase/tests/test_clip_types.py b/python_pkg/moviepy_showcase/tests/test_clip_types.py index e4daae1..587eb69 100644 --- a/python_pkg/moviepy_showcase/tests/test_clip_types.py +++ b/python_pkg/moviepy_showcase/tests/test_clip_types.py @@ -25,7 +25,7 @@ def test_part1_data_to_frame() -> None: """Extract and test the inner data_to_frame function.""" import moviepy as mp - mp.DataVideoClip.side_effect = lambda *a, **kw: create_mock_clip() + mp.DataVideoClip.side_effect = lambda *_a, **_kw: create_mock_clip() result = part1_clip_types() assert len(result) > 0 diff --git a/python_pkg/moviepy_showcase/tests/test_moviepy_showcase.py b/python_pkg/moviepy_showcase/tests/test_moviepy_showcase.py index 316d4a9..e7714ec 100644 --- a/python_pkg/moviepy_showcase/tests/test_moviepy_showcase.py +++ b/python_pkg/moviepy_showcase/tests/test_moviepy_showcase.py @@ -4,6 +4,7 @@ from __future__ import annotations import contextlib from pathlib import Path +import tempfile from typing import Any from unittest.mock import MagicMock, patch @@ -102,17 +103,18 @@ def test_resize_to_canvas() -> None: def test_render_part() -> None: s1 = create_mock_clip() s2 = create_mock_clip() - _render_part([s1, s2], "/tmp/test_part.mp4", "test") + _render_part([s1, s2], tempfile.gettempdir() + "/test_part.mp4", "test") s1.close.assert_called_once() s2.close.assert_called_once() # ── main ───────────────────────────────────────────────────────── def test_main_success() -> None: + mock_dir = tempfile.gettempdir() + "/mock_dir" with ( patch( "python_pkg.moviepy_showcase.moviepy_showcase.tempfile.mkdtemp", - return_value="/tmp/mock_dir", + return_value=mock_dir, ), patch( "python_pkg.moviepy_showcase.moviepy_showcase._build", @@ -122,15 +124,16 @@ def test_main_success() -> None: ) as mock_rmtree, ): main() - mock_build.assert_called_once_with("/tmp/mock_dir") - mock_rmtree.assert_called_once_with("/tmp/mock_dir", ignore_errors=True) + mock_build.assert_called_once_with(mock_dir) + mock_rmtree.assert_called_once_with(mock_dir, ignore_errors=True) def test_main_build_raises() -> None: + mock_dir = tempfile.gettempdir() + "/mock_dir" with ( patch( "python_pkg.moviepy_showcase.moviepy_showcase.tempfile.mkdtemp", - return_value="/tmp/mock_dir", + return_value=mock_dir, ), patch( "python_pkg.moviepy_showcase.moviepy_showcase._build", @@ -142,7 +145,7 @@ def test_main_build_raises() -> None: ): with contextlib.suppress(RuntimeError): main() - mock_rmtree.assert_called_once_with("/tmp/mock_dir", ignore_errors=True) + mock_rmtree.assert_called_once_with(mock_dir, ignore_errors=True) # ── _build ─────────────────────────────────────────────────────── @@ -155,4 +158,4 @@ def test_build() -> None: ), patch.object(Path, "stat", return_value=mock_stat), ): - _build("/tmp/test_build") + _build(tempfile.gettempdir() + "/test_build") diff --git a/python_pkg/music_gen/music_generator.py b/python_pkg/music_gen/music_generator.py index 272273b..98011c3 100755 --- a/python_pkg/music_gen/music_generator.py +++ b/python_pkg/music_gen/music_generator.py @@ -14,6 +14,8 @@ Usage: from __future__ import annotations import argparse +import importlib.util +import logging from pathlib import Path import sys import warnings @@ -49,6 +51,8 @@ from python_pkg.music_gen._music_speech import ( warnings.filterwarnings("ignore", category=FutureWarning) warnings.filterwarnings("ignore", category=UserWarning) +logger = logging.getLogger(__name__) + # Re-export all public symbols for backwards compatibility __all__ = [ "BARK_MAX_CHARS", @@ -85,8 +89,6 @@ def check_dependencies(*, include_bark: bool = False) -> bool: Args: include_bark: Whether to check for Bark dependencies as well. """ - import importlib.util - missing = [] if importlib.util.find_spec("torch") is None: @@ -102,87 +104,128 @@ def check_dependencies(*, include_bark: bool = False) -> bool: missing.append("git+https://github.com/suno-ai/bark.git") if missing: - print("Missing dependencies. Install with:") - print(f" pip install {' '.join(missing)}") - print("\nFor CUDA support:") - print(" pip install torch --index-url https://download.pytorch.org/whl/cu121") - print(" pip install transformers scipy") + logger.error("Missing dependencies. Install with:") + logger.error(" pip install %s", " ".join(missing)) + logger.error("For CUDA support:") + logger.error( + " pip install torch --index-url https://download.pytorch.org/whl/cu121", + ) + logger.error(" pip install transformers scipy") if include_bark: - print("\nFor Bark vocals:") - print(" pip install git+https://github.com/suno-ai/bark.git") + logger.error("For Bark vocals:") + logger.error( + " pip install git+https://github.com/suno-ai/bark.git", + ) return False return True +EXAMPLE_PROMPTS = [ + "upbeat electronic dance music with heavy bass", + "calm acoustic guitar melody with soft percussion", + "epic orchestral soundtrack with dramatic strings", + "lo-fi hip hop beats for studying", + "80s synthwave with retro vibes", + "jazz piano trio with upright bass", + "ambient electronic music for relaxation", + "rock guitar riff with drums", + "classical piano sonata in minor key", + "tropical house with steel drums", +] + + +def _show_help() -> None: + """Display example prompts.""" + logger.info("Example prompts:") + for i, ex in enumerate(EXAMPLE_PROMPTS, 1): + logger.info(" %d. %s", i, ex) + + +def _handle_duration(raw: str) -> int | None: + """Parse and return a new duration, or None on failure.""" + try: + value = int(raw.strip()) + except ValueError: + logger.warning( + "Invalid duration. Use ':d ' e.g., ':d 15'", + ) + return None + else: + clamped = max(1, min(30, value)) + logger.info("Duration set to %ds", clamped) + return clamped + + +def _resolve_prompt(prompt: str) -> str | None: + """Resolve a numeric prompt to an example, or return as-is. + + Returns None if the number is out of range. + """ + if prompt.isdigit(): + idx = int(prompt) - 1 + if 0 <= idx < len(EXAMPLE_PROMPTS): + resolved = EXAMPLE_PROMPTS[idx] + logger.info("Using: %s", resolved) + return resolved + logger.warning( + "Invalid number. Enter 1-%d", + len(EXAMPLE_PROMPTS), + ) + return None + return prompt + + def interactive_mode(model: object, processor: object) -> None: """Run interactive prompt mode.""" - print("\n" + "=" * 60) - print("INTERACTIVE MODE") - print("=" * 60) - print("Enter prompts to generate music. Commands:") - print(" :q or :quit - Exit") - print(" :d - Set duration (e.g., ':d 15')") - print(" :h or :help - Show example prompts") - print("=" * 60) + banner = "=" * 60 + logger.info("\n%s", banner) + logger.info("INTERACTIVE MODE") + logger.info("%s", banner) + logger.info("Enter prompts to generate music. Commands:") + logger.info(" :q or :quit - Exit") + logger.info(" :d - Set duration (e.g., ':d 15')") + logger.info(" :h or :help - Show example prompts") + logger.info("%s", banner) duration = 10 - example_prompts = [ - "upbeat electronic dance music with heavy bass", - "calm acoustic guitar melody with soft percussion", - "epic orchestral soundtrack with dramatic strings", - "lo-fi hip hop beats for studying", - "80s synthwave with retro vibes", - "jazz piano trio with upright bass", - "ambient electronic music for relaxation", - "rock guitar riff with drums", - "classical piano sonata in minor key", - "tropical house with steel drums", - ] - while True: try: prompt = input(f"\n[{duration}s] Enter prompt: ").strip() except (EOFError, KeyboardInterrupt): - print("\nExiting...") + logger.info("Exiting...") break if not prompt: continue if prompt.lower() in (":q", ":quit", "quit", "exit"): - print("Exiting...") + logger.info("Exiting...") break if prompt.lower() in (":h", ":help", "help"): - print("\nExample prompts:") - for i, ex in enumerate(example_prompts, 1): - print(f" {i}. {ex}") + _show_help() continue if prompt.startswith(":d "): - try: - duration = int(prompt[3:].strip()) - duration = max(1, min(30, duration)) # Clamp to 1-30 - print(f"Duration set to {duration}s") - except ValueError: - print("Invalid duration. Use ':d ' e.g., ':d 15'") + new_dur = _handle_duration(prompt[3:]) + if new_dur is not None: + duration = new_dur continue - # Check if user entered a number to use example prompt - if prompt.isdigit(): - idx = int(prompt) - 1 - if 0 <= idx < len(example_prompts): - prompt = example_prompts[idx] - print(f"Using: {prompt}") - else: - print(f"Invalid number. Enter 1-{len(example_prompts)}") - continue + resolved = _resolve_prompt(prompt) + if resolved is None: + continue try: - generate_music(prompt, model, processor, duration_seconds=duration) - except (RuntimeError, ValueError, OSError) as e: - print(f"Error generating music: {e}") + generate_music( + resolved, + model, + processor, + duration_seconds=duration, + ) + except (RuntimeError, ValueError, OSError): + logger.exception("Error generating music") def main() -> None: @@ -275,7 +318,9 @@ Bark tokens: [laughter] [laughs] [sighs] [music] [gasps] ♪ (singing) if not args.prompt and not args.interactive: parser.print_help() - print("\nError: Either provide a prompt or use --interactive mode") + logger.error( + "Either provide a prompt or use --interactive mode", + ) sys.exit(1) # Check dependencies diff --git a/python_pkg/music_gen/tests/test_music_generation.py b/python_pkg/music_gen/tests/test_music_generation.py index 10b6de7..8e524e1 100644 --- a/python_pkg/music_gen/tests/test_music_generation.py +++ b/python_pkg/music_gen/tests/test_music_generation.py @@ -57,9 +57,9 @@ class TestGetDevice: patch.dict("sys.modules", {"torch": mock_torch}), patch("shutil.which", return_value="/usr/bin/nvidia-smi"), patch("subprocess.run", return_value=mock_result), + pytest.raises(RuntimeError, match="NVIDIA GPU detected"), ): - with pytest.raises(RuntimeError, match="NVIDIA GPU detected"): - get_device() + get_device() def test_nvidia_smi_not_found(self) -> None: mock_torch = MagicMock() diff --git a/python_pkg/music_gen/tests/test_music_generator.py b/python_pkg/music_gen/tests/test_music_generator.py index 1f402bb..85d7621 100644 --- a/python_pkg/music_gen/tests/test_music_generator.py +++ b/python_pkg/music_gen/tests/test_music_generator.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any +import logging +from typing import TYPE_CHECKING from unittest.mock import MagicMock, patch from python_pkg.music_gen.music_generator import ( @@ -21,56 +22,64 @@ class TestCheckDependencies: with patch("importlib.util.find_spec", return_value=MagicMock()): assert check_dependencies() is True - def test_torch_missing(self, capsys: pytest.CaptureFixture[str]) -> None: - def mock_find_spec(name: str) -> Any: + def test_torch_missing(self, caplog: pytest.LogCaptureFixture) -> None: + def mock_find_spec(name: str) -> MagicMock | None: if name == "torch": return None return MagicMock() - with patch("importlib.util.find_spec", side_effect=mock_find_spec): + with ( + caplog.at_level(logging.DEBUG), + patch("importlib.util.find_spec", side_effect=mock_find_spec), + ): assert check_dependencies() is False - captured = capsys.readouterr() - assert "torch" in captured.out + assert "torch" in caplog.text - def test_transformers_missing(self, capsys: pytest.CaptureFixture[str]) -> None: - def mock_find_spec(name: str) -> Any: + def test_transformers_missing(self, caplog: pytest.LogCaptureFixture) -> None: + def mock_find_spec(name: str) -> MagicMock | None: if name == "transformers": return None return MagicMock() - with patch("importlib.util.find_spec", side_effect=mock_find_spec): + with ( + caplog.at_level(logging.DEBUG), + patch("importlib.util.find_spec", side_effect=mock_find_spec), + ): assert check_dependencies() is False - captured = capsys.readouterr() - assert "transformers" in captured.out + assert "transformers" in caplog.text - def test_scipy_missing(self, capsys: pytest.CaptureFixture[str]) -> None: - def mock_find_spec(name: str) -> Any: + def test_scipy_missing(self, caplog: pytest.LogCaptureFixture) -> None: + def mock_find_spec(name: str) -> MagicMock | None: if name == "scipy": return None return MagicMock() - with patch("importlib.util.find_spec", side_effect=mock_find_spec): + with ( + caplog.at_level(logging.DEBUG), + patch("importlib.util.find_spec", side_effect=mock_find_spec), + ): assert check_dependencies() is False - captured = capsys.readouterr() - assert "scipy" in captured.out + assert "scipy" in caplog.text def test_bark_missing_with_include_bark( self, - capsys: pytest.CaptureFixture[str], + caplog: pytest.LogCaptureFixture, ) -> None: - def mock_find_spec(name: str) -> Any: + def mock_find_spec(name: str) -> MagicMock | None: if name == "bark": return None return MagicMock() - with patch("importlib.util.find_spec", side_effect=mock_find_spec): + with ( + caplog.at_level(logging.DEBUG), + patch("importlib.util.find_spec", side_effect=mock_find_spec), + ): assert check_dependencies(include_bark=True) is False - captured = capsys.readouterr() - assert "bark" in captured.out.lower() + assert "bark" in caplog.text.lower() def test_bark_not_checked_without_flag(self) -> None: with patch("importlib.util.find_spec", return_value=MagicMock()): @@ -84,64 +93,78 @@ class TestCheckDependencies: class TestInteractiveMode: """Tests for interactive_mode().""" - def test_quit_command(self, capsys: pytest.CaptureFixture[str]) -> None: - with patch("builtins.input", return_value=":q"): + def test_quit_command(self, caplog: pytest.LogCaptureFixture) -> None: + with ( + caplog.at_level(logging.DEBUG), + patch("builtins.input", return_value=":q"), + ): interactive_mode(MagicMock(), MagicMock()) - captured = capsys.readouterr() - assert "Exiting" in captured.out + assert "Exiting" in caplog.text - def test_quit_word(self, capsys: pytest.CaptureFixture[str]) -> None: - with patch("builtins.input", return_value="quit"): + def test_quit_word(self, caplog: pytest.LogCaptureFixture) -> None: + with ( + caplog.at_level(logging.DEBUG), + patch("builtins.input", return_value="quit"), + ): interactive_mode(MagicMock(), MagicMock()) - captured = capsys.readouterr() - assert "Exiting" in captured.out + assert "Exiting" in caplog.text - def test_exit_word(self, capsys: pytest.CaptureFixture[str]) -> None: + def test_exit_word(self) -> None: with patch("builtins.input", return_value="exit"): interactive_mode(MagicMock(), MagicMock()) - def test_help_command(self, capsys: pytest.CaptureFixture[str]) -> None: - with patch("builtins.input", side_effect=[":h", ":q"]): + def test_help_command(self, caplog: pytest.LogCaptureFixture) -> None: + with ( + caplog.at_level(logging.DEBUG), + patch("builtins.input", side_effect=[":h", ":q"]), + ): interactive_mode(MagicMock(), MagicMock()) - captured = capsys.readouterr() - assert "Example prompts" in captured.out + assert "Example prompts" in caplog.text - def test_help_word(self, capsys: pytest.CaptureFixture[str]) -> None: - with patch("builtins.input", side_effect=["help", ":q"]): + def test_help_word(self, caplog: pytest.LogCaptureFixture) -> None: + with ( + caplog.at_level(logging.DEBUG), + patch("builtins.input", side_effect=["help", ":q"]), + ): interactive_mode(MagicMock(), MagicMock()) - captured = capsys.readouterr() - assert "Example prompts" in captured.out + assert "Example prompts" in caplog.text - def test_set_duration(self, capsys: pytest.CaptureFixture[str]) -> None: - with patch("builtins.input", side_effect=[":d 15", ":q"]): + def test_set_duration(self, caplog: pytest.LogCaptureFixture) -> None: + with ( + caplog.at_level(logging.DEBUG), + patch("builtins.input", side_effect=[":d 15", ":q"]), + ): interactive_mode(MagicMock(), MagicMock()) - captured = capsys.readouterr() - assert "Duration set to 15s" in captured.out + assert "Duration set to 15s" in caplog.text - def test_set_duration_clamped(self, capsys: pytest.CaptureFixture[str]) -> None: - with patch("builtins.input", side_effect=[":d 100", ":q"]): + def test_set_duration_clamped(self, caplog: pytest.LogCaptureFixture) -> None: + with ( + caplog.at_level(logging.DEBUG), + patch("builtins.input", side_effect=[":d 100", ":q"]), + ): interactive_mode(MagicMock(), MagicMock()) - captured = capsys.readouterr() - assert "Duration set to 30s" in captured.out + assert "Duration set to 30s" in caplog.text - def test_set_duration_invalid(self, capsys: pytest.CaptureFixture[str]) -> None: - with patch("builtins.input", side_effect=[":d abc", ":q"]): + def test_set_duration_invalid(self, caplog: pytest.LogCaptureFixture) -> None: + with ( + caplog.at_level(logging.DEBUG), + patch("builtins.input", side_effect=[":d abc", ":q"]), + ): interactive_mode(MagicMock(), MagicMock()) - captured = capsys.readouterr() - assert "Invalid duration" in captured.out + assert "Invalid duration" in caplog.text def test_empty_prompt(self) -> None: with patch("builtins.input", side_effect=["", ":q"]): interactive_mode(MagicMock(), MagicMock()) - def test_number_prompt_valid(self, capsys: pytest.CaptureFixture[str]) -> None: + def test_number_prompt_valid(self) -> None: with ( patch("builtins.input", side_effect=["1", ":q"]), patch( @@ -152,12 +175,14 @@ class TestInteractiveMode: mock_gen.assert_called_once() - def test_number_prompt_invalid(self, capsys: pytest.CaptureFixture[str]) -> None: - with patch("builtins.input", side_effect=["99", ":q"]): + def test_number_prompt_invalid(self, caplog: pytest.LogCaptureFixture) -> None: + with ( + caplog.at_level(logging.DEBUG), + patch("builtins.input", side_effect=["99", ":q"]), + ): interactive_mode(MagicMock(), MagicMock()) - captured = capsys.readouterr() - assert "Invalid number" in captured.out + assert "Invalid number" in caplog.text def test_normal_prompt(self) -> None: with ( @@ -170,8 +195,9 @@ class TestInteractiveMode: mock_gen.assert_called_once() - def test_generation_error(self, capsys: pytest.CaptureFixture[str]) -> None: + def test_generation_error(self, caplog: pytest.LogCaptureFixture) -> None: with ( + caplog.at_level(logging.DEBUG), patch("builtins.input", side_effect=["jazz music", ":q"]), patch( "python_pkg.music_gen.music_generator.generate_music", @@ -180,46 +206,56 @@ class TestInteractiveMode: ): interactive_mode(MagicMock(), MagicMock()) - captured = capsys.readouterr() - assert "Error generating music" in captured.out + assert "Error generating music" in caplog.text - def test_eof_error(self, capsys: pytest.CaptureFixture[str]) -> None: - with patch("builtins.input", side_effect=EOFError): - interactive_mode(MagicMock(), MagicMock()) - - captured = capsys.readouterr() - assert "Exiting" in captured.out - - def test_keyboard_interrupt(self, capsys: pytest.CaptureFixture[str]) -> None: - with patch("builtins.input", side_effect=KeyboardInterrupt): - interactive_mode(MagicMock(), MagicMock()) - - captured = capsys.readouterr() - assert "Exiting" in captured.out - - def test_quit_long(self, capsys: pytest.CaptureFixture[str]) -> None: - with patch("builtins.input", return_value=":quit"): - interactive_mode(MagicMock(), MagicMock()) - - captured = capsys.readouterr() - assert "Exiting" in captured.out - - def test_help_long(self, capsys: pytest.CaptureFixture[str]) -> None: - with patch("builtins.input", side_effect=[":help", ":q"]): - interactive_mode(MagicMock(), MagicMock()) - - captured = capsys.readouterr() - assert "Example prompts" in captured.out - - def test_duration_clamp_minimum(self, capsys: pytest.CaptureFixture[str]) -> None: - with patch("builtins.input", side_effect=[":d 0", ":q"]): - interactive_mode(MagicMock(), MagicMock()) - - captured = capsys.readouterr() - assert "Duration set to 1s" in captured.out - - def test_generation_value_error(self, capsys: pytest.CaptureFixture[str]) -> None: + def test_eof_error(self, caplog: pytest.LogCaptureFixture) -> None: with ( + caplog.at_level(logging.DEBUG), + patch("builtins.input", side_effect=EOFError), + ): + interactive_mode(MagicMock(), MagicMock()) + + assert "Exiting" in caplog.text + + def test_keyboard_interrupt(self, caplog: pytest.LogCaptureFixture) -> None: + with ( + caplog.at_level(logging.DEBUG), + patch("builtins.input", side_effect=KeyboardInterrupt), + ): + interactive_mode(MagicMock(), MagicMock()) + + assert "Exiting" in caplog.text + + def test_quit_long(self, caplog: pytest.LogCaptureFixture) -> None: + with ( + caplog.at_level(logging.DEBUG), + patch("builtins.input", return_value=":quit"), + ): + interactive_mode(MagicMock(), MagicMock()) + + assert "Exiting" in caplog.text + + def test_help_long(self, caplog: pytest.LogCaptureFixture) -> None: + with ( + caplog.at_level(logging.DEBUG), + patch("builtins.input", side_effect=[":help", ":q"]), + ): + interactive_mode(MagicMock(), MagicMock()) + + assert "Example prompts" in caplog.text + + def test_duration_clamp_minimum(self, caplog: pytest.LogCaptureFixture) -> None: + with ( + caplog.at_level(logging.DEBUG), + patch("builtins.input", side_effect=[":d 0", ":q"]), + ): + interactive_mode(MagicMock(), MagicMock()) + + assert "Duration set to 1s" in caplog.text + + def test_generation_value_error(self, caplog: pytest.LogCaptureFixture) -> None: + with ( + caplog.at_level(logging.DEBUG), patch("builtins.input", side_effect=["jazz", ":q"]), patch( "python_pkg.music_gen.music_generator.generate_music", @@ -228,11 +264,11 @@ class TestInteractiveMode: ): interactive_mode(MagicMock(), MagicMock()) - captured = capsys.readouterr() - assert "Error generating music" in captured.out + assert "Error generating music" in caplog.text - def test_generation_os_error(self, capsys: pytest.CaptureFixture[str]) -> None: + def test_generation_os_error(self, caplog: pytest.LogCaptureFixture) -> None: with ( + caplog.at_level(logging.DEBUG), patch("builtins.input", side_effect=["jazz", ":q"]), patch( "python_pkg.music_gen.music_generator.generate_music", @@ -241,5 +277,4 @@ class TestInteractiveMode: ): interactive_mode(MagicMock(), MagicMock()) - captured = capsys.readouterr() - assert "Error generating music" in captured.out + assert "Error generating music" in caplog.text diff --git a/python_pkg/music_gen/tests/test_music_generator_part2.py b/python_pkg/music_gen/tests/test_music_generator_part2.py index f258ce1..17698b3 100644 --- a/python_pkg/music_gen/tests/test_music_generator_part2.py +++ b/python_pkg/music_gen/tests/test_music_generator_part2.py @@ -2,6 +2,7 @@ from __future__ import annotations +import tempfile from unittest.mock import MagicMock, patch import pytest @@ -227,7 +228,7 @@ class TestMain: with ( patch( "sys.argv", - ["music_generator", "--output", "/tmp/out", "test"], + ["music_generator", "--output", tempfile.gettempdir() + "/out", "test"], ), patch( "python_pkg.music_gen.music_generator.check_dependencies", diff --git a/python_pkg/music_gen/tests/test_music_speech.py b/python_pkg/music_gen/tests/test_music_speech.py index fe57115..e186a13 100644 --- a/python_pkg/music_gen/tests/test_music_speech.py +++ b/python_pkg/music_gen/tests/test_music_speech.py @@ -426,7 +426,7 @@ class TestGenerateVocalsForSong: return_value=["Hello."], ), ): - vocals, sr = _generate_vocals_for_song("Hello.", "v2/en_speaker_6") + _, sr = _generate_vocals_for_song("Hello.", "v2/en_speaker_6") assert sr == 24000 # The original_load should have been called via patched_load @@ -487,6 +487,6 @@ class TestGenerateInstrumentalForSong: return_value=audio, ), ): - instrumental, sr = _generate_instrumental_for_song("test", 60) + _, sr = _generate_instrumental_for_song("test", 60) assert sr == 100 diff --git a/python_pkg/poker_modifier_app/_poker_gui.py b/python_pkg/poker_modifier_app/_poker_gui.py index 07da909..4a6a586 100644 --- a/python_pkg/poker_modifier_app/_poker_gui.py +++ b/python_pkg/poker_modifier_app/_poker_gui.py @@ -31,7 +31,7 @@ class PokerGuiMixin: self.root.title("🃏 Texas Hold'em Modifier") self.root.geometry("650x750") self.root.configure(bg="#0f4c3a") - self.root.resizable(True, True) + self.root.resizable(width=True, height=True) style = ttk.Style() style.theme_use("clam") @@ -188,7 +188,7 @@ class PokerGuiMixin: parent, bg="#2d2d2d", relief=tk.RIDGE, bd=3, height=150 ) self.result_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 20), padx=10) - self.result_frame.pack_propagate(False) + self.result_frame.pack_propagate(flag=False) self.result_label = tk.Label( self.result_frame, diff --git a/python_pkg/poker_modifier_app/tests/test_poker_gui_part2.py b/python_pkg/poker_modifier_app/tests/test_poker_gui_part2.py index 01404fc..57d3e34 100644 --- a/python_pkg/poker_modifier_app/tests/test_poker_gui_part2.py +++ b/python_pkg/poker_modifier_app/tests/test_poker_gui_part2.py @@ -3,9 +3,12 @@ from __future__ import annotations import sys -from typing import Any +from typing import TYPE_CHECKING from unittest.mock import MagicMock, patch +if TYPE_CHECKING: + from python_pkg.poker_modifier_app._poker_gui import PokerGuiMixin + def _install_tk_mocks() -> dict[str, MagicMock]: """Install mock tkinter modules and return them.""" @@ -26,19 +29,19 @@ def _install_tk_mocks() -> dict[str, MagicMock]: # Make constructors return fresh mocks each time mock_tk.Tk.return_value = MagicMock(name="root") - mock_tk.Frame.side_effect = lambda *a, **kw: MagicMock(name="Frame") - mock_tk.Label.side_effect = lambda *a, **kw: MagicMock(name="Label") - mock_tk.LabelFrame.side_effect = lambda *a, **kw: MagicMock(name="LabelFrame") - mock_tk.Scale.side_effect = lambda *a, **kw: MagicMock(name="Scale") - mock_tk.IntVar.side_effect = lambda *a, **kw: MagicMock(name="IntVar") - mock_tk.BooleanVar.side_effect = lambda *a, **kw: MagicMock(name="BooleanVar") - mock_tk.Checkbutton.side_effect = lambda *a, **kw: MagicMock(name="Checkbutton") - mock_tk.Button.side_effect = lambda *a, **kw: MagicMock(name="Button") + mock_tk.Frame.side_effect = lambda *_a, **_kw: MagicMock(name="Frame") + mock_tk.Label.side_effect = lambda *_a, **_kw: MagicMock(name="Label") + mock_tk.LabelFrame.side_effect = lambda *_a, **_kw: MagicMock(name="LabelFrame") + mock_tk.Scale.side_effect = lambda *_a, **_kw: MagicMock(name="Scale") + mock_tk.IntVar.side_effect = lambda *_a, **_kw: MagicMock(name="IntVar") + mock_tk.BooleanVar.side_effect = lambda *_a, **_kw: MagicMock(name="BooleanVar") + mock_tk.Checkbutton.side_effect = lambda *_a, **_kw: MagicMock(name="Checkbutton") + mock_tk.Button.side_effect = lambda *_a, **_kw: MagicMock(name="Button") return {"tk": mock_tk, "ttk": mock_ttk} -def _make_mixin() -> Any: +def _make_mixin() -> tuple[PokerGuiMixin, MagicMock, MagicMock]: """Create a PokerGuiMixin instance with mocked tkinter.""" tk_mocks = _install_tk_mocks() @@ -99,7 +102,7 @@ class TestSetupMainWindow: root.title.assert_called_once_with("🃏 Texas Hold'em Modifier") root.geometry.assert_called_once_with("650x750") root.configure.assert_called_once_with(bg="#0f4c3a") - root.resizable.assert_called_once_with(True, True) + root.resizable.assert_called_once_with(width=True, height=True) mock_ttk.Style.assert_called_once() mock_ttk.Style.return_value.theme_use.assert_called_once_with("clam") @@ -239,7 +242,7 @@ class TestCreateResultDisplay: frame_calls = mock_tk.Frame.call_args_list assert any(c[1].get("height") == 150 for c in frame_calls) assert hasattr(mixin, "result_frame") - mixin.result_frame.pack_propagate.assert_called_once_with(False) + mixin.result_frame.pack_propagate.assert_called_once_with(flag=False) # Result label label_calls = mock_tk.Label.call_args_list diff --git a/python_pkg/poker_modifier_app/tests/test_poker_modifier_app.py b/python_pkg/poker_modifier_app/tests/test_poker_modifier_app.py index 71fd9f0..8cb5d6c 100644 --- a/python_pkg/poker_modifier_app/tests/test_poker_modifier_app.py +++ b/python_pkg/poker_modifier_app/tests/test_poker_modifier_app.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Any +from typing import TYPE_CHECKING from unittest.mock import MagicMock, patch from python_pkg.poker_modifier_app._poker_modifiers import ( @@ -11,8 +11,11 @@ from python_pkg.poker_modifier_app._poker_modifiers import ( Modifier, ) +if TYPE_CHECKING: + from python_pkg.poker_modifier_app.poker_modifier_app import PokerModifierApp -def _make_app() -> Any: + +def _make_app() -> PokerModifierApp: """Create a PokerModifierApp with setup_gui mocked out.""" with patch( "python_pkg.poker_modifier_app.poker_modifier_app.PokerGuiMixin.setup_gui" @@ -422,7 +425,7 @@ class TestMainBlock: """Test the if __name__ == '__main__' block.""" @patch("python_pkg.poker_modifier_app.poker_modifier_app.PokerGuiMixin.setup_gui") - def test_main_block(self, _mock_setup: MagicMock) -> None: + def test_main_block(self, mock_setup: MagicMock) -> None: with patch( "python_pkg.poker_modifier_app.poker_modifier_app.PokerModifierApp.run" ): diff --git a/python_pkg/praca_magisterska_video/_q23_transformer.py b/python_pkg/praca_magisterska_video/_q23_transformer.py index 55f9fcf..fce06bc 100644 --- a/python_pkg/praca_magisterska_video/_q23_transformer.py +++ b/python_pkg/praca_magisterska_video/_q23_transformer.py @@ -221,14 +221,16 @@ def _transformer_seg_demo() -> list[CompositeVideoClip]: (100, 480), ), ( - "Z\u0142o\u017cono\u015b\u0107: O(n\u00b2) pami\u0119ci \u2014 n = liczba pikseli/token\u00f3w", + "Z\u0142o\u017cono\u015b\u0107: O(n\u00b2) pami\u0119ci \u2014 n = liczba " + "pikseli/token\u00f3w", 16, "#EF9A9A", FONT_R, (100, 535), ), ( - "Dlatego SegFormer u\u017cywa efficient attention (liniowa z\u0142o\u017cono\u015b\u0107)", + "Dlatego SegFormer u\u017cywa efficient attention (liniowa " + "z\u0142o\u017cono\u015b\u0107)", 15, "#78909C", FONT_R, @@ -269,14 +271,16 @@ def _transformer_seg_demo() -> list[CompositeVideoClip]: (80, 90), ), ( - "Encoder: obraz \u2192 cechy (zmniejsza rozdzielczo\u015b\u0107, wyci\u0105ga CO)", + "Encoder: obraz \u2192 cechy (zmniejsza rozdzielczo\u015b\u0107, " + "wyci\u0105ga CO)", 16, "#64B5F6", FONT_R, (100, 140), ), ( - "Decoder: cechy \u2192 mapa (zwi\u0119ksza rozdzielczo\u015b\u0107, odtwarza GDZIE)", + "Decoder: cechy \u2192 mapa (zwi\u0119ksza rozdzielczo\u015b\u0107, " + "odtwarza GDZIE)", 16, "#A5D6A7", FONT_R, @@ -334,7 +338,8 @@ def _transformer_seg_demo() -> list[CompositeVideoClip]: (80, 465), ), ( - " CNN lokal. \u2192 dilated (szersze RF) \u2192 transformer (global) \u2192 masked att.", + " CNN lokal. \u2192 dilated (szersze RF) \u2192 transformer (global) " + "\u2192 masked att.", 16, "#B0BEC5", FONT_R, diff --git a/python_pkg/praca_magisterska_video/tests/conftest.py b/python_pkg/praca_magisterska_video/tests/conftest.py index 3cc2e49..00a5a96 100644 --- a/python_pkg/praca_magisterska_video/tests/conftest.py +++ b/python_pkg/praca_magisterska_video/tests/conftest.py @@ -2,7 +2,9 @@ from __future__ import annotations +import contextlib import importlib +import importlib.util as _ilu from pathlib import Path import sys from typing import TYPE_CHECKING @@ -12,6 +14,7 @@ import numpy as np import pytest if TYPE_CHECKING: + from collections.abc import Callable from types import ModuleType # Add the source directory to sys.path so bare imports like @@ -35,7 +38,11 @@ def _make_moviepy_mocks() -> dict[str, ModuleType | MagicMock]: moviepy_mod = MagicMock() # VideoClip: needs to accept make_frame callable -> return mock with methods - def _video_clip_factory(make_frame=None, duration=None, **kw): + def _video_clip_factory( + make_frame: Callable[[float], np.ndarray] | None = None, + duration: float | None = None, + **_kw: object, + ) -> MagicMock: clip = MagicMock() clip.make_frame = make_frame clip.duration = duration @@ -57,14 +64,18 @@ def _make_moviepy_mocks() -> dict[str, ModuleType | MagicMock]: moviepy_mod.VideoClip = _video_clip_factory - def _color_clip_factory(size=None, color=None, **kw): + def _color_clip_factory( + _size: tuple[int, int] | None = None, + _color: tuple[int, ...] | None = None, + **_kw: object, + ) -> MagicMock: clip = MagicMock() clip.with_duration.return_value = clip return clip moviepy_mod.ColorClip = _color_clip_factory - def _text_clip_factory(**kw): + def _text_clip_factory(**_kw: object) -> MagicMock: clip = MagicMock() clip.with_duration.return_value = clip clip.with_position.return_value = clip @@ -72,7 +83,11 @@ def _make_moviepy_mocks() -> dict[str, ModuleType | MagicMock]: moviepy_mod.TextClip = _text_clip_factory - def _composite_factory(clips=None, size=None, **kw): + def _composite_factory( + _clips: list[MagicMock] | None = None, + _size: tuple[int, int] | None = None, + **_kw: object, + ) -> MagicMock: clip = MagicMock() clip.with_effects.return_value = clip clip.with_duration.return_value = clip @@ -81,7 +96,11 @@ def _make_moviepy_mocks() -> dict[str, ModuleType | MagicMock]: moviepy_mod.CompositeVideoClip = _composite_factory - def _concat_factory(clips=None, method=None, **kw): + def _concat_factory( + _clips: list[MagicMock] | None = None, + _method: str | None = None, + **_kw: object, + ) -> MagicMock: clip = MagicMock() clip.write_videofile = MagicMock() return clip @@ -117,7 +136,6 @@ for _name, _mock in _MOVIEPY_MOCKS.items(): # modules (``_q24_classical.py``, etc.) find ``BG_COLOR`` etc. # 3. Register both under their full package paths for coverage. # --------------------------------------------------------------------------- -import importlib.util as _ilu # Load generate_images _q24_common first. _gen_q24_spec = _ilu.spec_from_file_location( @@ -127,9 +145,9 @@ _gen_q24_spec = _ilu.spec_from_file_location( assert _gen_q24_spec is not None assert _gen_q24_spec.loader is not None _q24_common_gen = _ilu.module_from_spec(_gen_q24_spec) -_gen_q24_spec.loader.exec_module(_q24_common_gen) -# Cache as bare name so generate_images imports work during _BARE_MODULES. +# Register BEFORE exec so @dataclass can resolve __module__ in Python 3.14+. sys.modules["_q24_common"] = _q24_common_gen +_gen_q24_spec.loader.exec_module(_q24_common_gen) # Load top-level _q24_common. _top_q24_spec = _ilu.spec_from_file_location( @@ -139,6 +157,8 @@ _top_q24_spec = _ilu.spec_from_file_location( assert _top_q24_spec is not None assert _top_q24_spec.loader is not None _q24_common_top = _ilu.module_from_spec(_top_q24_spec) +# Register BEFORE exec so @dataclass can resolve __module__ in Python 3.14+. +sys.modules["_q24_common_top"] = _q24_common_top _top_q24_spec.loader.exec_module(_q24_common_top) @@ -205,11 +225,9 @@ _BARE_MODULES = [ "generate_scheduling_diagrams", ] for _bare in _BARE_MODULES: - try: + with contextlib.suppress(ImportError): _mod = importlib.import_module(_bare) sys.modules.setdefault(f"{_GEN_PKG}.{_bare}", _mod) - except ImportError: - pass # Now swap _q24_common to the top-level version so that top-level source # modules (``_q24_classical.py`` etc.) find BG_COLOR, W, H, etc. @@ -242,6 +260,7 @@ def _no_savefig(monkeypatch: pytest.MonkeyPatch) -> None: def _compat_auto_set_font_size( self: matplotlib.table.Table, + *, value: bool = True, **_kw: object, ) -> None: diff --git a/python_pkg/praca_magisterska_video/tests/test_anki_generator_part2.py b/python_pkg/praca_magisterska_video/tests/test_anki_generator_part2.py index 635f1a7..3ce1a1e 100644 --- a/python_pkg/praca_magisterska_video/tests/test_anki_generator_part2.py +++ b/python_pkg/praca_magisterska_video/tests/test_anki_generator_part2.py @@ -117,7 +117,7 @@ def test_get_file_metadata_no_match(tmp_path: Path) -> None: p = tmp_path / "readme.txt" p.write_text("No Przedmiot here", encoding="utf-8") - num, subject, content = get_file_metadata(str(p)) + num, subject, _ = get_file_metadata(str(p)) assert num == "00" assert subject == "Ogólne" @@ -386,7 +386,7 @@ def test_generate_anki_basic(tmp_path: Path) -> None: out_dir.mkdir() with ( - patch(f"{_PKG}.Path.__truediv__", side_effect=lambda self, x: tmp_path / x), + patch(f"{_PKG}.Path.__truediv__", side_effect=lambda _self, x: tmp_path / x), patch( f"{_PKG}.generate_anki.__defaults__", (False, False, False), diff --git a/python_pkg/praca_magisterska_video/tests/test_gen_agent.py b/python_pkg/praca_magisterska_video/tests/test_gen_agent.py index 8829b9e..a16f656 100644 --- a/python_pkg/praca_magisterska_video/tests/test_gen_agent.py +++ b/python_pkg/praca_magisterska_video/tests/test_gen_agent.py @@ -14,6 +14,27 @@ mpl.use("Agg") import matplotlib.pyplot as plt import pytest +from python_pkg.praca_magisterska_video.generate_images._agent_cognitive import ( + draw_bdi_model, + draw_behavior_tree, +) +from python_pkg.praca_magisterska_video.generate_images._agent_reactive import ( + draw_3t_architecture, + draw_see_think_act, +) +from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import ( + BG, + DPI, + GRAY5, + OUTPUT_DIR, + ArrowCfg, + BoxStyle, + DashedArrowCfg, + draw_arrow, + draw_box, + draw_dashed_arrow, +) + pytestmark = pytest.mark.usefixtures("_no_savefig") _MOD = "python_pkg.praca_magisterska_video.generate_images" @@ -26,79 +47,41 @@ class TestAgentHelpers: """Test draw_box, draw_arrow, draw_dashed_arrow and dataclasses.""" def test_draw_box_rounded(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import ( - BoxStyle, - draw_box, - ) - fig, ax = plt.subplots() draw_box(ax, (0, 0), (1, 1), "hi", BoxStyle(rounded=True)) plt.close(fig) def test_draw_box_not_rounded(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import ( - BoxStyle, - draw_box, - ) - fig, ax = plt.subplots() draw_box(ax, (0, 0), (1, 1), "hi", BoxStyle(rounded=False)) plt.close(fig) def test_draw_box_no_style(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import ( - draw_box, - ) - fig, ax = plt.subplots() draw_box(ax, (0, 0), (1, 1), "hi") plt.close(fig) def test_draw_arrow_with_label(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import ( - ArrowCfg, - draw_arrow, - ) - fig, ax = plt.subplots() draw_arrow(ax, (0, 0), (1, 1), ArrowCfg(label="lbl")) plt.close(fig) def test_draw_arrow_no_label(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import ( - draw_arrow, - ) - fig, ax = plt.subplots() draw_arrow(ax, (0, 0), (1, 1)) plt.close(fig) def test_draw_dashed_arrow_with_label(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import ( - DashedArrowCfg, - draw_dashed_arrow, - ) - fig, ax = plt.subplots() draw_dashed_arrow(ax, (0, 0), (1, 1), DashedArrowCfg(label="lbl")) plt.close(fig) def test_draw_dashed_arrow_no_label(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import ( - draw_dashed_arrow, - ) - fig, ax = plt.subplots() draw_dashed_arrow(ax, (0, 0), (1, 1)) plt.close(fig) def test_dataclass_defaults(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import ( - ArrowCfg, - BoxStyle, - DashedArrowCfg, - ) - bs = BoxStyle() assert bs.rounded is True assert bs.fill == "white" @@ -108,13 +91,6 @@ class TestAgentHelpers: assert dc.label == "" def test_module_constants(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import ( - BG, - DPI, - GRAY5, - OUTPUT_DIR, - ) - assert DPI == 300 assert BG == "white" assert isinstance(GRAY5, str) @@ -128,17 +104,9 @@ class TestAgentReactive: """Test draw_see_think_act and draw_3t_architecture.""" def test_draw_see_think_act(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._agent_reactive import ( - draw_see_think_act, - ) - draw_see_think_act() def test_draw_3t_architecture(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._agent_reactive import ( - draw_3t_architecture, - ) - draw_3t_architecture() @@ -149,15 +117,7 @@ class TestAgentCognitive: """Test draw_behavior_tree (covers all node types) and draw_bdi_model.""" def test_draw_behavior_tree(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._agent_cognitive import ( - draw_behavior_tree, - ) - draw_behavior_tree() def test_draw_bdi_model(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._agent_cognitive import ( - draw_bdi_model, - ) - draw_bdi_model() diff --git a/python_pkg/praca_magisterska_video/tests/test_gen_anki.py b/python_pkg/praca_magisterska_video/tests/test_gen_anki.py index 1894e74..914ec6e 100644 --- a/python_pkg/praca_magisterska_video/tests/test_gen_anki.py +++ b/python_pkg/praca_magisterska_video/tests/test_gen_anki.py @@ -122,7 +122,7 @@ class TestAnkiApproach1: patch.object( Path, "open", - side_effect=lambda *a, **kw: StringIO(fake_md), + side_effect=lambda *_a, **_kw: StringIO(fake_md), ), ): main() @@ -187,7 +187,7 @@ class TestAnkiApproach1: patch.object( Path, "open", - side_effect=lambda *a, **kw: StringIO(fake_md), + side_effect=lambda *_a, **_kw: StringIO(fake_md), ), ): main() @@ -324,7 +324,7 @@ class TestAnkiApproach2: patch.object( Path, "open", - side_effect=lambda *a, **kw: StringIO(fake_md), + side_effect=lambda *_a, **_kw: StringIO(fake_md), ), ): main() @@ -387,7 +387,7 @@ class TestAnkiApproach2: patch.object( Path, "open", - side_effect=lambda *a, **kw: StringIO(fake_md), + side_effect=lambda *_a, **_kw: StringIO(fake_md), ), ): main() diff --git a/python_pkg/praca_magisterska_video/tests/test_gen_automata.py b/python_pkg/praca_magisterska_video/tests/test_gen_automata.py index e55835d..238e586 100644 --- a/python_pkg/praca_magisterska_video/tests/test_gen_automata.py +++ b/python_pkg/praca_magisterska_video/tests/test_gen_automata.py @@ -17,6 +17,47 @@ mpl.use("Agg") import matplotlib.pyplot as plt import pytest +from python_pkg.praca_magisterska_video.generate_images import ( + generate_automata_diagrams as _auto_diags, +) +from python_pkg.praca_magisterska_video.generate_images._automata_common import ( + BG, + DPI, + FS, + FS_SMALL, + FS_TITLE, + GRAY1, + GRAY2, + GRAY3, + GRAY4, + GRAY5, + INNER_RATIO, + LIGHT_BLUE, + LIGHT_GREEN, + LIGHT_RED, + LIGHT_YELLOW, + LN, + OUTPUT_DIR, + ArrowStyle, + LoopStyle, + StateStyle, + draw_curved_arrow, + draw_self_loop, + draw_state_circle, +) +from python_pkg.praca_magisterska_video.generate_images._automata_fa import ( + draw_fa_recognition, +) +from python_pkg.praca_magisterska_video.generate_images._automata_lba import ( + draw_lba_recognition, +) +from python_pkg.praca_magisterska_video.generate_images._automata_pda import ( + draw_pda_recognition, +) +from python_pkg.praca_magisterska_video.generate_images._automata_tm import ( + draw_tm_recognition, +) + pytestmark = pytest.mark.usefixtures("_no_savefig") @@ -27,10 +68,6 @@ class TestAutomataCommon: """Test draw_state_circle, draw_curved_arrow, draw_self_loop.""" def test_state_circle_basic(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._automata_common import ( - draw_state_circle, - ) - fig, ax = plt.subplots() ax.set_xlim(-2, 2) ax.set_ylim(-2, 2) @@ -38,11 +75,6 @@ class TestAutomataCommon: plt.close(fig) def test_state_circle_accepting(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._automata_common import ( - StateStyle, - draw_state_circle, - ) - fig, ax = plt.subplots() ax.set_xlim(-2, 2) ax.set_ylim(-2, 2) @@ -50,11 +82,6 @@ class TestAutomataCommon: plt.close(fig) def test_state_circle_initial(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._automata_common import ( - StateStyle, - draw_state_circle, - ) - fig, ax = plt.subplots() ax.set_xlim(-2, 2) ax.set_ylim(-2, 2) @@ -62,11 +89,6 @@ class TestAutomataCommon: plt.close(fig) def test_state_circle_both(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._automata_common import ( - StateStyle, - draw_state_circle, - ) - fig, ax = plt.subplots() ax.set_xlim(-2, 2) ax.set_ylim(-2, 2) @@ -76,20 +98,11 @@ class TestAutomataCommon: plt.close(fig) def test_curved_arrow(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._automata_common import ( - draw_curved_arrow, - ) - fig, ax = plt.subplots() draw_curved_arrow(ax, (0, 0), (1, 1), "a") plt.close(fig) def test_self_loop_top(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._automata_common import ( - LoopStyle, - draw_self_loop, - ) - fig, ax = plt.subplots() ax.set_xlim(-2, 2) ax.set_ylim(-2, 2) @@ -97,11 +110,6 @@ class TestAutomataCommon: plt.close(fig) def test_self_loop_bottom(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._automata_common import ( - LoopStyle, - draw_self_loop, - ) - fig, ax = plt.subplots() ax.set_xlim(-2, 2) ax.set_ylim(-2, 2) @@ -109,10 +117,6 @@ class TestAutomataCommon: plt.close(fig) def test_self_loop_default(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._automata_common import ( - draw_self_loop, - ) - fig, ax = plt.subplots() ax.set_xlim(-2, 2) ax.set_ylim(-2, 2) @@ -121,11 +125,6 @@ class TestAutomataCommon: def test_self_loop_unknown_direction(self) -> None: """Cover implicit else when direction is not top/bottom.""" - from python_pkg.praca_magisterska_video.generate_images._automata_common import ( - LoopStyle, - draw_self_loop, - ) - fig, ax = plt.subplots() ax.set_xlim(-2, 2) ax.set_ylim(-2, 2) @@ -133,12 +132,6 @@ class TestAutomataCommon: plt.close(fig) def test_dataclass_defaults(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._automata_common import ( - ArrowStyle, - LoopStyle, - StateStyle, - ) - ss = StateStyle() assert ss.accepting is False assert ss.initial is False @@ -148,26 +141,6 @@ class TestAutomataCommon: assert ls.direction == "top" def test_module_constants(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._automata_common import ( - BG, - DPI, - FS, - FS_SMALL, - FS_TITLE, - GRAY1, - GRAY2, - GRAY3, - GRAY4, - GRAY5, - INNER_RATIO, - LIGHT_BLUE, - LIGHT_GREEN, - LIGHT_RED, - LIGHT_YELLOW, - LN, - OUTPUT_DIR, - ) - assert DPI == 300 assert BG == "white" assert isinstance(FS, int | float) @@ -194,31 +167,15 @@ class TestAutomataDiagrams: """Test all recognition diagram functions.""" def test_fa_recognition(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._automata_fa import ( - draw_fa_recognition, - ) - draw_fa_recognition() def test_pda_recognition(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._automata_pda import ( - draw_pda_recognition, - ) - draw_pda_recognition() def test_lba_recognition(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._automata_lba import ( - draw_lba_recognition, - ) - draw_lba_recognition() def test_tm_recognition(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._automata_tm import ( - draw_tm_recognition, - ) - draw_tm_recognition() @@ -229,15 +186,9 @@ class TestAutomataEntry: """Verify generate_automata_diagrams exports are accessible.""" def test_all_exports(self) -> None: - import python_pkg.praca_magisterska_video.generate_images.generate_automata_diagrams as mod - - assert hasattr(mod, "__all__") - for name in mod.__all__: - assert hasattr(mod, name) + assert hasattr(_auto_diags, "__all__") + for name in _auto_diags.__all__: + assert hasattr(_auto_diags, name) def test_output_dir(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_automata_diagrams import ( - OUTPUT_DIR, - ) - - assert isinstance(OUTPUT_DIR, str) + assert isinstance(_auto_diags.OUTPUT_DIR, str) diff --git a/python_pkg/praca_magisterska_video/tests/test_gen_bf_negative.py b/python_pkg/praca_magisterska_video/tests/test_gen_bf_negative.py index 6152136..27bcca7 100644 --- a/python_pkg/praca_magisterska_video/tests/test_gen_bf_negative.py +++ b/python_pkg/praca_magisterska_video/tests/test_gen_bf_negative.py @@ -13,6 +13,15 @@ mpl.use("Agg") import matplotlib.pyplot as plt import pytest +from python_pkg.praca_magisterska_video.generate_images import ( + generate_bf_negative_diagram as _bf_neg, +) +from python_pkg.praca_magisterska_video.generate_images._bf_negative_diagrams import ( + _add_annotation_box, + generate_bf_negative_cycle, + generate_bf_negative_weights, +) + pytestmark = pytest.mark.usefixtures("_no_savefig") _MOD = "python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram" @@ -25,157 +34,100 @@ class TestBFHelpers: """Test draw_node, _choose_edge_style, draw_edge, draw_neg_graph.""" def test_draw_node_default(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import ( - draw_node, - ) - fig, ax = plt.subplots() ax.set_xlim(-1, 5) ax.set_ylim(-1, 5) - draw_node(ax, "S", (1, 1)) + _bf_neg.draw_node(ax, "S", (1, 1)) plt.close(fig) def test_draw_node_current(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import ( - draw_node, - ) - fig, ax = plt.subplots() ax.set_xlim(-1, 5) ax.set_ylim(-1, 5) - draw_node(ax, "A", (1, 1), current=True, dist_label="2") + _bf_neg.draw_node(ax, "A", (1, 1), current=True, dist_label="2") plt.close(fig) def test_draw_node_visited(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import ( - draw_node, - ) - fig, ax = plt.subplots() ax.set_xlim(-1, 5) ax.set_ylim(-1, 5) - draw_node(ax, "B", (1, 1), visited=True, dist_label="5") + _bf_neg.draw_node(ax, "B", (1, 1), visited=True, dist_label="5") plt.close(fig) def test_draw_node_error(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import ( - draw_node, - ) - fig, ax = plt.subplots() ax.set_xlim(-1, 5) ax.set_ylim(-1, 5) - draw_node(ax, "C", (1, 1), error=True, dist_label="?") + _bf_neg.draw_node(ax, "C", (1, 1), error=True, dist_label="?") plt.close(fig) def test_draw_node_no_dist_label(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import ( - draw_node, - ) - fig, ax = plt.subplots() ax.set_xlim(-1, 5) ax.set_ylim(-1, 5) - draw_node(ax, "X", (1, 1), visited=True) + _bf_neg.draw_node(ax, "X", (1, 1), visited=True) plt.close(fig) def test_choose_edge_style_cycle(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import ( - _choose_edge_style, - ) - - color, lw, ls = _choose_edge_style( + _, lw, ls = _bf_neg._choose_edge_style( negative=False, relaxed=False, highlighted=False, cycle_edge=True ) assert ls == "--" assert lw == 2.5 def test_choose_edge_style_negative(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import ( - _choose_edge_style, - ) - - color, lw, ls = _choose_edge_style( + _, lw, ls = _bf_neg._choose_edge_style( negative=True, relaxed=False, highlighted=False, cycle_edge=False ) assert lw == 2.5 assert ls == "-" def test_choose_edge_style_relaxed(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import ( - _choose_edge_style, - ) - - color, lw, ls = _choose_edge_style( + _, lw, _ = _bf_neg._choose_edge_style( negative=False, relaxed=True, highlighted=False, cycle_edge=False ) assert lw == 2.5 def test_choose_edge_style_highlighted(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import ( - _choose_edge_style, - ) - - color, lw, ls = _choose_edge_style( + color, _, ls = _bf_neg._choose_edge_style( negative=False, relaxed=False, highlighted=True, cycle_edge=False ) assert ls == "-" assert color == "#1565C0" def test_choose_edge_style_default(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import ( - GRAY3, - _choose_edge_style, - ) - - color, lw, ls = _choose_edge_style( + color, lw, _ = _bf_neg._choose_edge_style( negative=False, relaxed=False, highlighted=False, cycle_edge=False ) - assert color == GRAY3 + assert color == _bf_neg.GRAY3 assert lw == 1.5 def test_draw_edge_no_offset(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import ( - draw_edge, - ) - fig, ax = plt.subplots() ax.set_xlim(-1, 5) ax.set_ylim(-1, 5) - draw_edge(ax, (0, 0), (2, 2), 3) + _bf_neg.draw_edge(ax, (0, 0), (2, 2), 3) plt.close(fig) def test_draw_edge_with_offset(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import ( - draw_edge, - ) - fig, ax = plt.subplots() ax.set_xlim(-1, 5) ax.set_ylim(-1, 5) - draw_edge(ax, (0, 0), (2, 2), -3, negative=True, offset=0.3) + _bf_neg.draw_edge(ax, (0, 0), (2, 2), -3, negative=True, offset=0.3) plt.close(fig) def test_draw_edge_highlighted(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import ( - draw_edge, - ) - fig, ax = plt.subplots() ax.set_xlim(-1, 5) ax.set_ylim(-1, 5) - draw_edge(ax, (0, 0), (2, 2), 5, highlighted=True) + _bf_neg.draw_edge(ax, (0, 0), (2, 2), 5, highlighted=True) plt.close(fig) def test_draw_edge_cycle(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import ( - draw_edge, - ) - fig, ax = plt.subplots() ax.set_xlim(-1, 5) ax.set_ylim(-1, 5) - draw_edge(ax, (0, 0), (2, 2), -2, cycle_edge=True) + _bf_neg.draw_edge(ax, (0, 0), (2, 2), -2, cycle_edge=True) plt.close(fig) @@ -184,36 +136,20 @@ class TestDrawNegGraph: def test_minimal(self) -> None: """All-defaults: visited, relaxed, dist, error_nodes all None.""" - from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import ( - NEG_EDGES, - draw_neg_graph, - ) - fig, ax = plt.subplots() - draw_neg_graph(ax, NEG_EDGES) + _bf_neg.draw_neg_graph(ax, _bf_neg.NEG_EDGES) plt.close(fig) def test_with_title(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import ( - NEG_EDGES, - draw_neg_graph, - ) - fig, ax = plt.subplots() - draw_neg_graph(ax, NEG_EDGES, title="Test") + _bf_neg.draw_neg_graph(ax, _bf_neg.NEG_EDGES, title="Test") plt.close(fig) def test_with_all_options(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import ( - NEG_EDGES, - NEG_POS, - draw_neg_graph, - ) - fig, ax = plt.subplots() - draw_neg_graph( + _bf_neg.draw_neg_graph( ax, - NEG_EDGES, + _bf_neg.NEG_EDGES, title="Full", dist={"S": "0", "A": "1", "B": "5", "C": "4"}, current="S", @@ -221,19 +157,15 @@ class TestDrawNegGraph: relaxed_edges={("S", "A")}, error_nodes={"C"}, extra_edges=[("C", "B", -3)], - node_positions=NEG_POS, + node_positions=_bf_neg.NEG_POS, ) plt.close(fig) def test_explicit_node_positions(self) -> None: """Cover node_positions is not None branch.""" - from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import ( - draw_neg_graph, - ) - pos = {"X": (1.0, 1.0), "Y": (3.0, 1.0)} fig, ax = plt.subplots() - draw_neg_graph( + _bf_neg.draw_neg_graph( ax, [("X", "Y", 2)], node_positions=pos, @@ -250,24 +182,12 @@ class TestBFDiagramFunctions: """Test the main diagram generation functions.""" def test_generate_bf_negative_weights(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._bf_negative_diagrams import ( - generate_bf_negative_weights, - ) - generate_bf_negative_weights() def test_generate_bf_negative_cycle(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._bf_negative_diagrams import ( - generate_bf_negative_cycle, - ) - generate_bf_negative_cycle() def test_add_annotation_box(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._bf_negative_diagrams import ( - _add_annotation_box, - ) - fig, ax = plt.subplots() _add_annotation_box(ax, 1, 1, "test", color="red", bg_color="white") plt.close(fig) @@ -277,40 +197,20 @@ class TestBFModuleConstants: """Verify module-level constants.""" def test_constants(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import ( - BG, - DPI, - FS, - FS_EDGE, - FS_SMALL, - FS_TITLE, - GRAY1, - GRAY2, - GRAY3, - GRAY4, - LIGHT_GREEN, - LIGHT_RED, - LIGHT_YELLOW, - LN, - NEG_EDGES, - NEG_POS, - OUTPUT_DIR, - ) - - assert DPI == 300 - assert BG == "white" - assert isinstance(FS, int | float) - assert isinstance(FS_EDGE, int | float) - assert isinstance(FS_SMALL, int | float) - assert isinstance(FS_TITLE, int | float) - assert isinstance(GRAY1, str) - assert isinstance(GRAY2, str) - assert isinstance(GRAY3, str) - assert isinstance(GRAY4, str) - assert isinstance(LIGHT_GREEN, str) - assert isinstance(LIGHT_RED, str) - assert isinstance(LIGHT_YELLOW, str) - assert isinstance(LN, str) - assert isinstance(OUTPUT_DIR, str) - assert len(NEG_EDGES) > 0 - assert len(NEG_POS) > 0 + assert _bf_neg.DPI == 300 + assert _bf_neg.BG == "white" + assert isinstance(_bf_neg.FS, int | float) + assert isinstance(_bf_neg.FS_EDGE, int | float) + assert isinstance(_bf_neg.FS_SMALL, int | float) + assert isinstance(_bf_neg.FS_TITLE, int | float) + assert isinstance(_bf_neg.GRAY1, str) + assert isinstance(_bf_neg.GRAY2, str) + assert isinstance(_bf_neg.GRAY3, str) + assert isinstance(_bf_neg.GRAY4, str) + assert isinstance(_bf_neg.LIGHT_GREEN, str) + assert isinstance(_bf_neg.LIGHT_RED, str) + assert isinstance(_bf_neg.LIGHT_YELLOW, str) + assert isinstance(_bf_neg.LN, str) + assert isinstance(_bf_neg.OUTPUT_DIR, str) + assert len(_bf_neg.NEG_EDGES) > 0 + assert len(_bf_neg.NEG_POS) > 0 diff --git a/python_pkg/praca_magisterska_video/tests/test_gen_norm.py b/python_pkg/praca_magisterska_video/tests/test_gen_norm.py index 7ceee15..c2f3308 100644 --- a/python_pkg/praca_magisterska_video/tests/test_gen_norm.py +++ b/python_pkg/praca_magisterska_video/tests/test_gen_norm.py @@ -17,6 +17,24 @@ mpl.use("Agg") import matplotlib.pyplot as plt import pytest +from python_pkg.praca_magisterska_video.generate_images import ( + generate_normalization_diagrams as _norm_mod, +) +from python_pkg.praca_magisterska_video.generate_images._norm_advanced import ( + draw_3nf, + draw_4nf, + draw_bcnf, +) +from python_pkg.praca_magisterska_video.generate_images._norm_basic import ( + draw_0nf, + draw_1nf, + draw_2nf, +) +from python_pkg.praca_magisterska_video.generate_images._norm_higher import ( + draw_5nf, + draw_summary_flow, +) + pytestmark = pytest.mark.usefixtures("_no_savefig") _GEN = ( @@ -34,51 +52,28 @@ class TestNormHelpers: """Test _compute_col_widths, draw_table, create_figure, add_arrow, add_label.""" def test_compute_col_widths_normal(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import ( - _compute_col_widths, - ) - - result = _compute_col_widths(["Name", "Age"], [["Alice", "30"]]) + result = _norm_mod._compute_col_widths(["Name", "Age"], [["Alice", "30"]]) assert len(result) == 2 assert all(w >= 0.5 for w in result) def test_compute_col_widths_jagged(self) -> None: """Row shorter than headers → c < len(r) False branch.""" - from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import ( - _compute_col_widths, - ) - - result = _compute_col_widths(["A", "B", "C"], [["x"]]) + result = _norm_mod._compute_col_widths(["A", "B", "C"], [["x"]]) assert len(result) == 3 def test_draw_table_auto_widths(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import ( - create_figure, - draw_table, - ) - - fig, ax = create_figure() - draw_table(ax, 0, 5, "T", ["A", "B"], [["1", "2"]]) + fig, ax = _norm_mod.create_figure() + _norm_mod.draw_table(ax, 0, 5, "T", ["A", "B"], [["1", "2"]]) plt.close(fig) def test_draw_table_explicit_widths(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import ( - create_figure, - draw_table, - ) - - fig, ax = create_figure() - draw_table(ax, 0, 5, "T", ["A"], [["x"]], col_widths=[1.0]) + fig, ax = _norm_mod.create_figure() + _norm_mod.draw_table(ax, 0, 5, "T", ["A"], [["x"]], col_widths=[1.0]) plt.close(fig) def test_draw_table_highlight_cols(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import ( - create_figure, - draw_table, - ) - - fig, ax = create_figure() - draw_table( + fig, ax = _norm_mod.create_figure() + _norm_mod.draw_table( ax, 0, 5, @@ -90,13 +85,8 @@ class TestNormHelpers: plt.close(fig) def test_draw_table_highlight_rows(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import ( - create_figure, - draw_table, - ) - - fig, ax = create_figure() - draw_table( + fig, ax = _norm_mod.create_figure() + _norm_mod.draw_table( ax, 0, 5, @@ -108,13 +98,8 @@ class TestNormHelpers: plt.close(fig) def test_draw_table_highlight_cells(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import ( - create_figure, - draw_table, - ) - - fig, ax = create_figure() - draw_table( + fig, ax = _norm_mod.create_figure() + _norm_mod.draw_table( ax, 0, 5, @@ -126,13 +111,8 @@ class TestNormHelpers: plt.close(fig) def test_draw_table_strikethrough(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import ( - create_figure, - draw_table, - ) - - fig, ax = create_figure() - draw_table( + fig, ax = _norm_mod.create_figure() + _norm_mod.draw_table( ax, 0, 5, @@ -145,13 +125,8 @@ class TestNormHelpers: def test_draw_table_all_options(self) -> None: """All highlight/strikethrough at once, with matching+non-matching cells.""" - from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import ( - create_figure, - draw_table, - ) - - fig, ax = create_figure() - w, h = draw_table( + fig, ax = _norm_mod.create_figure() + w, h = _norm_mod.draw_table( ax, 0, 5, @@ -169,65 +144,35 @@ class TestNormHelpers: plt.close(fig) def test_create_figure(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import ( - create_figure, - ) - - fig, ax = create_figure(10, 8) + fig, ax = _norm_mod.create_figure(10, 8) assert fig is not None assert ax is not None plt.close(fig) def test_add_arrow_with_label(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import ( - add_arrow, - create_figure, - ) - - fig, ax = create_figure() - add_arrow(ax, 0, 5, 3, 5, "lbl", color="black") + fig, ax = _norm_mod.create_figure() + _norm_mod.add_arrow(ax, 0, 5, 3, 5, "lbl", color="black") plt.close(fig) def test_add_arrow_no_label(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import ( - add_arrow, - create_figure, - ) - - fig, ax = create_figure() - add_arrow(ax, 0, 5, 3, 5) + fig, ax = _norm_mod.create_figure() + _norm_mod.add_arrow(ax, 0, 5, 3, 5) plt.close(fig) def test_add_label(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import ( - add_label, - create_figure, - ) - - fig, ax = create_figure() - add_label(ax, 0, 5, "note", fontsize=10, color="red") + fig, ax = _norm_mod.create_figure() + _norm_mod.add_label(ax, 0, 5, "note", fontsize=10, color="red") plt.close(fig) def test_module_constants(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import ( - CELL_COLOR, - DPI, - FD_ARROW_COLOR, - FIXED_COLOR, - FONT_SIZE, - HEADER_COLOR, - HIGHLIGHT_COLOR, - OUTPUT_DIR, - ) - - assert DPI == 300 - assert isinstance(OUTPUT_DIR, str) - assert isinstance(HEADER_COLOR, str) - assert isinstance(CELL_COLOR, str) - assert isinstance(HIGHLIGHT_COLOR, str) - assert isinstance(FIXED_COLOR, str) - assert isinstance(FD_ARROW_COLOR, str) - assert isinstance(FONT_SIZE, int | float) + assert _norm_mod.DPI == 300 + assert isinstance(_norm_mod.OUTPUT_DIR, str) + assert isinstance(_norm_mod.HEADER_COLOR, str) + assert isinstance(_norm_mod.CELL_COLOR, str) + assert isinstance(_norm_mod.HIGHLIGHT_COLOR, str) + assert isinstance(_norm_mod.FIXED_COLOR, str) + assert isinstance(_norm_mod.FD_ARROW_COLOR, str) + assert isinstance(_norm_mod.FONT_SIZE, int | float) # ── _norm_basic (draw_table has positional-arg signature mismatch) ───── @@ -243,29 +188,17 @@ class TestNormBasic: @patch(f"{_BASIC}.add_arrow") @patch(f"{_BASIC}.draw_table") - def test_draw_0nf(self, _mock_dt: MagicMock, _mock_aa: MagicMock) -> None: - from python_pkg.praca_magisterska_video.generate_images._norm_basic import ( - draw_0nf, - ) - + def test_draw_0nf(self, mock_dt: MagicMock, mock_aa: MagicMock) -> None: draw_0nf() @patch(f"{_BASIC}.add_arrow") @patch(f"{_BASIC}.draw_table") - def test_draw_1nf(self, _mock_dt: MagicMock, _mock_aa: MagicMock) -> None: - from python_pkg.praca_magisterska_video.generate_images._norm_basic import ( - draw_1nf, - ) - + def test_draw_1nf(self, mock_dt: MagicMock, mock_aa: MagicMock) -> None: draw_1nf() @patch(f"{_BASIC}.add_arrow") @patch(f"{_BASIC}.draw_table") - def test_draw_2nf(self, _mock_dt: MagicMock, _mock_aa: MagicMock) -> None: - from python_pkg.praca_magisterska_video.generate_images._norm_basic import ( - draw_2nf, - ) - + def test_draw_2nf(self, mock_dt: MagicMock, mock_aa: MagicMock) -> None: draw_2nf() @@ -277,29 +210,17 @@ class TestNormAdvanced: @patch(f"{_ADV}.add_arrow") @patch(f"{_ADV}.draw_table") - def test_draw_3nf(self, _mock_dt: MagicMock, _mock_aa: MagicMock) -> None: - from python_pkg.praca_magisterska_video.generate_images._norm_advanced import ( - draw_3nf, - ) - + def test_draw_3nf(self, mock_dt: MagicMock, mock_aa: MagicMock) -> None: draw_3nf() @patch(f"{_ADV}.add_arrow") @patch(f"{_ADV}.draw_table") - def test_draw_bcnf(self, _mock_dt: MagicMock, _mock_aa: MagicMock) -> None: - from python_pkg.praca_magisterska_video.generate_images._norm_advanced import ( - draw_bcnf, - ) - + def test_draw_bcnf(self, mock_dt: MagicMock, mock_aa: MagicMock) -> None: draw_bcnf() @patch(f"{_ADV}.add_arrow") @patch(f"{_ADV}.draw_table") - def test_draw_4nf(self, _mock_dt: MagicMock, _mock_aa: MagicMock) -> None: - from python_pkg.praca_magisterska_video.generate_images._norm_advanced import ( - draw_4nf, - ) - + def test_draw_4nf(self, mock_dt: MagicMock, mock_aa: MagicMock) -> None: draw_4nf() @@ -311,18 +232,10 @@ class TestNormHigher: @patch(f"{_HIGH}.add_arrow") @patch(f"{_HIGH}.draw_table") - def test_draw_5nf(self, _mock_dt: MagicMock, _mock_aa: MagicMock) -> None: - from python_pkg.praca_magisterska_video.generate_images._norm_higher import ( - draw_5nf, - ) - + def test_draw_5nf(self, mock_dt: MagicMock, mock_aa: MagicMock) -> None: draw_5nf() @patch(f"{_HIGH}.add_arrow") @patch(f"{_HIGH}.draw_table") - def test_draw_summary_flow(self, _mock_dt: MagicMock, _mock_aa: MagicMock) -> None: - from python_pkg.praca_magisterska_video.generate_images._norm_higher import ( - draw_summary_flow, - ) - + def test_draw_summary_flow(self, mock_dt: MagicMock, mock_aa: MagicMock) -> None: draw_summary_flow() diff --git a/python_pkg/praca_magisterska_video/tests/test_gen_pattern.py b/python_pkg/praca_magisterska_video/tests/test_gen_pattern.py index 816ce0b..a8ae250 100644 --- a/python_pkg/praca_magisterska_video/tests/test_gen_pattern.py +++ b/python_pkg/praca_magisterska_video/tests/test_gen_pattern.py @@ -16,6 +16,19 @@ mpl.use("Agg") import matplotlib.pyplot as plt import pytest +from python_pkg.praca_magisterska_video.generate_images import ( + _pattern_pillars_observer as _pat_pillars, +) +from python_pkg.praca_magisterska_video.generate_images import ( + _pattern_template_catalog as _pat_tmpl, +) +from python_pkg.praca_magisterska_video.generate_images import ( + generate_pattern_diagrams as _pat_diags, +) +from python_pkg.praca_magisterska_video.generate_images._pattern_navigation import ( + generate_pattern_language_navigation, +) + pytestmark = pytest.mark.usefixtures("_no_savefig") _GEN = "python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams" @@ -31,74 +44,47 @@ class TestPatternConstants: """Constants and module-level values.""" def test_dpi(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import ( - DPI, - ) - - assert DPI == 300 + assert _pat_diags.DPI == 300 def test_bg(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import ( - BG, - ) - - assert BG == "white" + assert _pat_diags.BG == "white" def test_gray_constants(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import ( - GRAY1, - GRAY2, - GRAY3, - GRAY4, - GRAY5, + assert all( + isinstance(g, str) + for g in [ + _pat_diags.GRAY1, + _pat_diags.GRAY2, + _pat_diags.GRAY3, + _pat_diags.GRAY4, + _pat_diags.GRAY5, + ] ) - assert all(isinstance(g, str) for g in [GRAY1, GRAY2, GRAY3, GRAY4, GRAY5]) - def test_band_heights(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import ( - _BAND_HEIGHTS, - ) - - assert len(_BAND_HEIGHTS) == 5 - assert all(isinstance(h, float) for h in _BAND_HEIGHTS) + assert len(_pat_diags._BAND_HEIGHTS) == 5 + assert all(isinstance(h, float) for h in _pat_diags._BAND_HEIGHTS) def test_output_dir_is_str(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import ( - OUTPUT_DIR, - ) - - assert isinstance(OUTPUT_DIR, str) + assert isinstance(_pat_diags.OUTPUT_DIR, str) class TestDrawBox: """Test draw_box helper.""" def test_rounded(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import ( - draw_box, - ) - fig, ax = plt.subplots() - draw_box(ax, 0, 0, 1, 1, "test", rounded=True) + _pat_diags.draw_box(ax, 0, 0, 1, 1, "test", rounded=True) plt.close(fig) def test_not_rounded(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import ( - draw_box, - ) - fig, ax = plt.subplots() - draw_box(ax, 0, 0, 1, 1, "test", rounded=False) + _pat_diags.draw_box(ax, 0, 0, 1, 1, "test", rounded=False) plt.close(fig) def test_custom_style(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import ( - draw_box, - ) - fig, ax = plt.subplots() - draw_box( + _pat_diags.draw_box( ax, 0, 0, @@ -120,21 +106,13 @@ class TestDrawArrow: """Test draw_arrow helper.""" def test_default(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import ( - draw_arrow, - ) - fig, ax = plt.subplots() - draw_arrow(ax, 0, 0, 1, 1) + _pat_diags.draw_arrow(ax, 0, 0, 1, 1) plt.close(fig) def test_custom(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import ( - draw_arrow, - ) - fig, ax = plt.subplots() - draw_arrow(ax, 0, 0, 1, 1, lw=2.5, style="<->", color="red") + _pat_diags.draw_arrow(ax, 0, 0, 1, 1, lw=2.5, style="<->", color="red") plt.close(fig) @@ -145,22 +123,14 @@ class TestPatternTemplate: """Test generate_pattern_template.""" def test_runs(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._pattern_template_catalog import ( - generate_pattern_template, - ) - - generate_pattern_template() + _pat_tmpl.generate_pattern_template() class TestCatalogMap: """Test generate_catalog_map.""" def test_runs(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._pattern_template_catalog import ( - generate_catalog_map, - ) - - generate_catalog_map() + _pat_tmpl.generate_catalog_map() # ── _pattern_pillars_observer ────────────────────────────────────────── @@ -170,34 +140,22 @@ class TestThreePillars: """Test generate_three_pillars.""" def test_runs(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._pattern_pillars_observer import ( - generate_three_pillars, - ) - - generate_three_pillars() + _pat_pillars.generate_three_pillars() class TestObserverCard: """Test generate_observer_card_filled.""" def test_runs(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._pattern_pillars_observer import ( - generate_observer_card_filled, - ) - - generate_observer_card_filled() + _pat_pillars.generate_observer_card_filled() class TestGetObserverBandHeight: """Test _get_observer_band_height.""" def test_all_indices(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._pattern_pillars_observer import ( - _get_observer_band_height, - ) - for i in range(5): - h = _get_observer_band_height(i) + h = _pat_pillars._get_observer_band_height(i) assert isinstance(h, float) assert h > 0 @@ -209,8 +167,4 @@ class TestPatternLanguageNavigation: """Test generate_pattern_language_navigation.""" def test_runs(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._pattern_navigation import ( - generate_pattern_language_navigation, - ) - generate_pattern_language_navigation() diff --git a/python_pkg/praca_magisterska_video/tests/test_gen_process.py b/python_pkg/praca_magisterska_video/tests/test_gen_process.py index daaf17c..e329db2 100644 --- a/python_pkg/praca_magisterska_video/tests/test_gen_process.py +++ b/python_pkg/praca_magisterska_video/tests/test_gen_process.py @@ -16,6 +16,36 @@ mpl.use("Agg") import matplotlib.pyplot as plt import pytest +from python_pkg.praca_magisterska_video.generate_images import ( + generate_process_diagrams as _proc_diags, +) +from python_pkg.praca_magisterska_video.generate_images._process_bpmn_uml import ( + _draw_bpmn_elements, + _draw_bpmn_legend, + _draw_bpmn_pool_and_lanes, + _draw_uml_elements, + _draw_uml_legend, + generate_bpmn, + generate_uml_activity, +) +from python_pkg.praca_magisterska_video.generate_images._process_epc_fc import ( + _draw_epc_branches, + _draw_epc_connector, + _draw_epc_event, + _draw_epc_flow, + _draw_epc_function, + _draw_epc_legend, + generate_epc, +) +from python_pkg.praca_magisterska_video.generate_images._process_fc import ( + _draw_fc_elements, + _draw_fc_io_shape, + _draw_fc_legend, + _draw_fc_process_box, + _draw_fc_terminal, + generate_flowchart, +) + pytestmark = pytest.mark.usefixtures("_no_savefig") _GEN = "python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams" @@ -31,37 +61,21 @@ class TestProcessConstants: """Constants and module-level values.""" def test_dpi(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import ( - DPI, - ) - - assert DPI == 300 + assert _proc_diags.DPI == 300 def test_bg_color(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import ( - BG_COLOR, - ) - - assert BG_COLOR == "white" + assert _proc_diags.BG_COLOR == "white" def test_output_dir(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import ( - OUTPUT_DIR, - ) - - assert isinstance(OUTPUT_DIR, str) + assert isinstance(_proc_diags.OUTPUT_DIR, str) class TestProcessDrawArrow: """Test draw_arrow helper.""" def test_default(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import ( - draw_arrow, - ) - fig, ax = plt.subplots() - draw_arrow(ax, 0, 0, 1, 1) + _proc_diags.draw_arrow(ax, 0, 0, 1, 1) plt.close(fig) @@ -69,12 +83,8 @@ class TestProcessDrawLine: """Test draw_line helper.""" def test_default(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import ( - draw_line, - ) - fig, ax = plt.subplots() - draw_line(ax, 0, 0, 5, 5) + _proc_diags.draw_line(ax, 0, 0, 5, 5) plt.close(fig) @@ -82,21 +92,15 @@ class TestProcessDrawRoundedRect: """Test draw_rounded_rect helper.""" def test_default(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import ( - draw_rounded_rect, - ) - fig, ax = plt.subplots() - draw_rounded_rect(ax, 5, 5, 10, 4, "Hello") + _proc_diags.draw_rounded_rect(ax, 5, 5, 10, 4, "Hello") plt.close(fig) def test_custom_params(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import ( - draw_rounded_rect, - ) - fig, ax = plt.subplots() - draw_rounded_rect(ax, 0, 0, 8, 3, "styled", fill="#CCC", lw=3, fontsize=12) + _proc_diags.draw_rounded_rect( + ax, 0, 0, 8, 3, "styled", fill="#CCC", lw=3, fontsize=12 + ) plt.close(fig) @@ -104,30 +108,18 @@ class TestProcessDrawDiamond: """Test draw_diamond helper.""" def test_with_text(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import ( - draw_diamond, - ) - fig, ax = plt.subplots() - draw_diamond(ax, 5, 5, 3, "XOR") + _proc_diags.draw_diamond(ax, 5, 5, 3, "XOR") plt.close(fig) def test_without_text(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import ( - draw_diamond, - ) - fig, ax = plt.subplots() - draw_diamond(ax, 5, 5, 3) + _proc_diags.draw_diamond(ax, 5, 5, 3) plt.close(fig) def test_custom_fill(self) -> None: - from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import ( - draw_diamond, - ) - fig, ax = plt.subplots() - draw_diamond(ax, 5, 5, 3, "Y", fill="#EEE", fontsize=12) + _proc_diags.draw_diamond(ax, 5, 5, 3, "Y", fill="#EEE", fontsize=12) plt.close(fig) @@ -138,17 +130,9 @@ class TestBPMN: """Test generate_bpmn and its sub-helpers.""" def test_generate_bpmn(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._process_bpmn_uml import ( - generate_bpmn, - ) - generate_bpmn() def test_draw_bpmn_pool_and_lanes(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._process_bpmn_uml import ( - _draw_bpmn_pool_and_lanes, - ) - fig, ax = plt.subplots() ax.set_xlim(0, 110) ax.set_ylim(0, 75) @@ -157,10 +141,6 @@ class TestBPMN: plt.close(fig) def test_draw_bpmn_elements(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._process_bpmn_uml import ( - _draw_bpmn_elements, - ) - fig, ax = plt.subplots() ax.set_xlim(0, 110) ax.set_ylim(0, 75) @@ -168,10 +148,6 @@ class TestBPMN: plt.close(fig) def test_draw_bpmn_legend(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._process_bpmn_uml import ( - _draw_bpmn_legend, - ) - fig, ax = plt.subplots() ax.set_xlim(0, 110) ax.set_ylim(0, 75) @@ -183,17 +159,9 @@ class TestUMLActivity: """Test generate_uml_activity and its sub-helpers.""" def test_generate_uml_activity(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._process_bpmn_uml import ( - generate_uml_activity, - ) - generate_uml_activity() def test_draw_uml_elements(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._process_bpmn_uml import ( - _draw_uml_elements, - ) - fig, ax = plt.subplots() ax.set_xlim(0, 100) ax.set_ylim(0, 100) @@ -201,10 +169,6 @@ class TestUMLActivity: plt.close(fig) def test_draw_uml_legend(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._process_bpmn_uml import ( - _draw_uml_legend, - ) - fig, ax = plt.subplots() ax.set_xlim(0, 100) ax.set_ylim(0, 100) @@ -219,44 +183,24 @@ class TestEPC: """Test generate_epc and its sub-helpers.""" def test_generate_epc(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._process_epc_fc import ( - generate_epc, - ) - generate_epc() def test_draw_epc_event(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._process_epc_fc import ( - _draw_epc_event, - ) - fig, ax = plt.subplots() _draw_epc_event(ax, 50, 50, "test event") plt.close(fig) def test_draw_epc_function(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._process_epc_fc import ( - _draw_epc_function, - ) - fig, ax = plt.subplots() _draw_epc_function(ax, 50, 50, "test function") plt.close(fig) def test_draw_epc_connector(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._process_epc_fc import ( - _draw_epc_connector, - ) - fig, ax = plt.subplots() _draw_epc_connector(ax, 50, 50, "XOR") plt.close(fig) def test_draw_epc_flow(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._process_epc_fc import ( - _draw_epc_flow, - ) - fig, ax = plt.subplots() ax.set_xlim(0, 100) ax.set_ylim(0, 120) @@ -267,10 +211,6 @@ class TestEPC: plt.close(fig) def test_draw_epc_branches(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._process_epc_fc import ( - _draw_epc_branches, - ) - fig, ax = plt.subplots() ax.set_xlim(0, 100) ax.set_ylim(0, 120) @@ -278,10 +218,6 @@ class TestEPC: plt.close(fig) def test_draw_epc_legend(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._process_epc_fc import ( - _draw_epc_legend, - ) - fig, ax = plt.subplots() ax.set_xlim(0, 100) ax.set_ylim(0, 120) @@ -296,44 +232,24 @@ class TestFlowchart: """Test generate_flowchart and its sub-helpers.""" def test_generate_flowchart(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._process_fc import ( - generate_flowchart, - ) - generate_flowchart() def test_draw_fc_terminal(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._process_fc import ( - _draw_fc_terminal, - ) - fig, ax = plt.subplots() _draw_fc_terminal(ax, 50, 50, "START") plt.close(fig) def test_draw_fc_process_box(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._process_fc import ( - _draw_fc_process_box, - ) - fig, ax = plt.subplots() _draw_fc_process_box(ax, 50, 50, "Process") plt.close(fig) def test_draw_fc_io_shape(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._process_fc import ( - _draw_fc_io_shape, - ) - fig, ax = plt.subplots() _draw_fc_io_shape(ax, 50, 50, "I/O") plt.close(fig) def test_draw_fc_elements(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._process_fc import ( - _draw_fc_elements, - ) - fig, ax = plt.subplots() ax.set_xlim(0, 100) ax.set_ylim(0, 110) @@ -341,10 +257,6 @@ class TestFlowchart: plt.close(fig) def test_draw_fc_legend(self) -> None: - from python_pkg.praca_magisterska_video.generate_images._process_fc import ( - _draw_fc_legend, - ) - fig, ax = plt.subplots() ax.set_xlim(0, 100) ax.set_ylim(0, 110) diff --git a/python_pkg/praca_magisterska_video/tests/test_gen_split_questions.py b/python_pkg/praca_magisterska_video/tests/test_gen_split_questions.py index 92473c9..3dc4c0c 100644 --- a/python_pkg/praca_magisterska_video/tests/test_gen_split_questions.py +++ b/python_pkg/praca_magisterska_video/tests/test_gen_split_questions.py @@ -48,7 +48,7 @@ class TestSplitQuestions: source_file = FakeFile(source_content) written_files: dict[str, FakeFile] = {} - def fake_open(self_path: Path, *args: object, **kwargs: object) -> FakeFile: + def fake_open(self_path: Path, *_args: object, **_kwargs: object) -> FakeFile: path_str = str(self_path) if "OBRONA_MAGISTERSKA_ODPOWIEDZI" in path_str: return source_file @@ -59,7 +59,7 @@ class TestSplitQuestions: with ( patch.object(Path, "open", fake_open), - patch.object(Path, "mkdir", lambda *a, **kw: None), + patch.object(Path, "mkdir", lambda *_a, **_kw: None), ): importlib.import_module(mod_name) diff --git a/python_pkg/praca_magisterska_video/tests/test_generate_anki_final_part2.py b/python_pkg/praca_magisterska_video/tests/test_generate_anki_final_part2.py index dbf97c7..6cf7710 100644 --- a/python_pkg/praca_magisterska_video/tests/test_generate_anki_final_part2.py +++ b/python_pkg/praca_magisterska_video/tests/test_generate_anki_final_part2.py @@ -144,7 +144,7 @@ def test_get_file_metadata(sample_file: Path) -> None: _get_file_metadata, ) - num, subject, content = _get_file_metadata(str(sample_file)) + num, subject, _ = _get_file_metadata(str(sample_file)) assert num == "01" assert subject == "Informatyka" @@ -157,7 +157,7 @@ def test_get_file_metadata_no_match(tmp_path: Path) -> None: p = tmp_path / "readme.txt" p.write_text("No Przedmiot", encoding="utf-8") - num, subject, content = _get_file_metadata(str(p)) + num, subject, _ = _get_file_metadata(str(p)) assert num == "00" assert subject == "Ogólne" diff --git a/python_pkg/praca_magisterska_video/tests/test_generate_anki_final_part3.py b/python_pkg/praca_magisterska_video/tests/test_generate_anki_final_part3.py index eec605d..36a619e 100644 --- a/python_pkg/praca_magisterska_video/tests/test_generate_anki_final_part3.py +++ b/python_pkg/praca_magisterska_video/tests/test_generate_anki_final_part3.py @@ -207,7 +207,7 @@ def test_body_parts_long_para_truncation() -> None: # --- _extract_subsection_cards: empty parts / multiple parts --- -def test_subsection_empty_answer_parts(tmp_path: Path) -> None: +def test_subsection_empty_answer_parts() -> None: """Subsection where _extract_body_parts returns [] (182->173).""" from python_pkg.praca_magisterska_video.generate_images.generate_anki_final import ( _extract_subsection_cards, @@ -403,7 +403,7 @@ def test_main_function(tmp_path: Path) -> None: call_count = 0 - def fake_extract(filepath: object) -> list[dict[str, str]]: + def fake_extract(_filepath: object) -> list[dict[str, str]]: nonlocal call_count call_count += 1 if call_count == 1: diff --git a/python_pkg/praca_magisterska_video/tests/test_generate_anki_part2.py b/python_pkg/praca_magisterska_video/tests/test_generate_anki_part2.py index aff43ea..6567b3e 100644 --- a/python_pkg/praca_magisterska_video/tests/test_generate_anki_part2.py +++ b/python_pkg/praca_magisterska_video/tests/test_generate_anki_part2.py @@ -79,7 +79,7 @@ def test_get_metadata(sample_file: Path) -> None: _get_metadata, ) - num, topic, title, main_q, content = _get_metadata(str(sample_file)) + num, topic, _, main_q, content = _get_metadata(str(sample_file)) assert num == "01" assert "test" in topic assert "main concept" in main_q @@ -92,7 +92,7 @@ def test_get_metadata_no_match(minimal_file: Path) -> None: _get_metadata, ) - num, topic, title, main_q, content = _get_metadata(str(minimal_file)) + num, topic, _, _, _ = _get_metadata(str(minimal_file)) assert num == "00" assert topic == "unknown" @@ -104,7 +104,7 @@ def test_extract_main_card(sample_file: Path) -> None: _get_metadata, ) - num, topic, title, main_q, content = _get_metadata(str(sample_file)) + num, topic, _, main_q, content = _get_metadata(str(sample_file)) cards = _extract_main_card(content, main_q, "Informatyka", num, topic) assert len(cards) == 1 assert "main concept" in cards[0]["question"] diff --git a/python_pkg/praca_magisterska_video/tests/test_generate_anki_part3.py b/python_pkg/praca_magisterska_video/tests/test_generate_anki_part3.py index 0b2a910..06997ce 100644 --- a/python_pkg/praca_magisterska_video/tests/test_generate_anki_part3.py +++ b/python_pkg/praca_magisterska_video/tests/test_generate_anki_part3.py @@ -115,7 +115,7 @@ def test_main_card_def_outside_length(def_length_file: Path) -> None: _get_metadata, ) - num, topic, title, main_q, content = _get_metadata(str(def_length_file)) + num, topic, _, main_q, content = _get_metadata(str(def_length_file)) cards = _extract_main_card(content, main_q, "Informatyka", num, topic) assert isinstance(cards, list) @@ -135,7 +135,7 @@ def test_sub_cards_short_body(subsection_file: Path) -> None: assert isinstance(cards, list) -def test_sub_cards_no_answer_text(tmp_path: Path) -> None: +def test_sub_cards_no_answer_text() -> None: """Subsection where _extract_subsection_answer returns None (line 145).""" from python_pkg.praca_magisterska_video.generate_images.generate_anki import ( _extract_sub_cards, @@ -221,7 +221,7 @@ def test_main_function(tmp_path: Path) -> None: call_count = 0 - def fake_extract(filepath: object) -> list[dict[str, str]]: + def fake_extract(_filepath: object) -> list[dict[str, str]]: nonlocal call_count call_count += 1 if call_count == 1: diff --git a/python_pkg/praca_magisterska_video/tests/test_generate_anki_v2_part2.py b/python_pkg/praca_magisterska_video/tests/test_generate_anki_v2_part2.py index 2107a12..bbc1b39 100644 --- a/python_pkg/praca_magisterska_video/tests/test_generate_anki_v2_part2.py +++ b/python_pkg/praca_magisterska_video/tests/test_generate_anki_v2_part2.py @@ -255,7 +255,7 @@ def test_main_error_branch(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> N return real_path(out_file) return real_path(s) - def failing_process(filepath: str) -> list[dict[str, str]]: + def failing_process(_filepath: str) -> list[dict[str, str]]: msg = "test error" raise ValueError(msg) diff --git a/python_pkg/praca_magisterska_video/tests/test_generate_anki_v3_part2.py b/python_pkg/praca_magisterska_video/tests/test_generate_anki_v3_part2.py index 412d874..cc93e58 100644 --- a/python_pkg/praca_magisterska_video/tests/test_generate_anki_v3_part2.py +++ b/python_pkg/praca_magisterska_video/tests/test_generate_anki_v3_part2.py @@ -113,9 +113,11 @@ Some introductory text that is ignored completely. - **Gamma** """ -_PLAIN_BODY = """\ -This is a plain first paragraph without any structured content and it is long enough to be captured by regex. -""" +_PLAIN_BODY = ( + "This is a plain first paragraph without any" + " structured content and it is long enough" + " to be captured by regex.\n" +) _PARA_ONLY_MD = """\ # Pytanie 03: Para Only @@ -251,7 +253,7 @@ def test_read_file_metadata_matching(sample_file: Path) -> None: _read_file_metadata, ) - content, base_tags, main_question = _read_file_metadata(sample_file) + _, base_tags, main_question = _read_file_metadata(sample_file) assert "pyt01" in base_tags assert "Informatyka" in base_tags assert main_question is not None @@ -266,7 +268,7 @@ def test_read_file_metadata_no_match(tmp_path: Path) -> None: p = tmp_path / "readme.txt" p.write_text(_MINIMAL_MD, encoding="utf-8") - content, base_tags, main_question = _read_file_metadata(p) + _, base_tags, main_question = _read_file_metadata(p) assert "pyt00" in base_tags assert "Og\u00f3lne" in base_tags assert main_question is None diff --git a/python_pkg/praca_magisterska_video/tests/test_generate_anki_v3_part3.py b/python_pkg/praca_magisterska_video/tests/test_generate_anki_v3_part3.py index cd4c362..4a95a75 100644 --- a/python_pkg/praca_magisterska_video/tests/test_generate_anki_v3_part3.py +++ b/python_pkg/praca_magisterska_video/tests/test_generate_anki_v3_part3.py @@ -224,7 +224,7 @@ def test_main_error_branch(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> N return real_path(out_file) return real_path(s) - def failing_extract(filepath: object) -> list[dict[str, str]]: + def failing_extract(_filepath: object) -> list[dict[str, str]]: msg = "test error" raise ValueError(msg) diff --git a/python_pkg/praca_magisterska_video/tests/test_q23_classical_part2.py b/python_pkg/praca_magisterska_video/tests/test_q23_classical_part2.py index 6a05694..b7155fa 100644 --- a/python_pkg/praca_magisterska_video/tests/test_q23_classical_part2.py +++ b/python_pkg/praca_magisterska_video/tests/test_q23_classical_part2.py @@ -2,16 +2,24 @@ from __future__ import annotations +from typing import TYPE_CHECKING from unittest.mock import MagicMock, patch import numpy as np +if TYPE_CHECKING: + from collections.abc import Callable + def _spy_vc() -> tuple[object, list[tuple[object, float]]]: """VideoClip spy capturing make_frame closures.""" captured: list[tuple[object, float]] = [] - def spy(make_frame=None, duration=None, **_kw: object) -> MagicMock: + def spy( + make_frame: Callable[[float], np.ndarray] | None = None, + duration: float | None = None, + **_kw: object, + ) -> MagicMock: if callable(make_frame): captured.append((make_frame, duration or 1.0)) clip = MagicMock() diff --git a/python_pkg/praca_magisterska_video/tests/test_q24_classical_part2.py b/python_pkg/praca_magisterska_video/tests/test_q24_classical_part2.py index 9772fef..cb90e46 100644 --- a/python_pkg/praca_magisterska_video/tests/test_q24_classical_part2.py +++ b/python_pkg/praca_magisterska_video/tests/test_q24_classical_part2.py @@ -2,16 +2,24 @@ from __future__ import annotations +from typing import TYPE_CHECKING from unittest.mock import MagicMock, patch import numpy as np +if TYPE_CHECKING: + from collections.abc import Callable + def _spy_vc() -> tuple[object, list[tuple[object, float]]]: """VideoClip spy capturing make_frame closures.""" captured: list[tuple[object, float]] = [] - def spy(make_frame=None, duration=None, **_kw: object) -> MagicMock: + def spy( + make_frame: Callable[[float], np.ndarray] | None = None, + duration: float | None = None, + **_kw: object, + ) -> MagicMock: if callable(make_frame): captured.append((make_frame, duration or 1.0)) clip = MagicMock() diff --git a/python_pkg/praca_magisterska_video/tests/test_q24_nms_final_part2.py b/python_pkg/praca_magisterska_video/tests/test_q24_nms_final_part2.py index 8e6d810..72f2c37 100644 --- a/python_pkg/praca_magisterska_video/tests/test_q24_nms_final_part2.py +++ b/python_pkg/praca_magisterska_video/tests/test_q24_nms_final_part2.py @@ -2,16 +2,24 @@ from __future__ import annotations +from typing import TYPE_CHECKING from unittest.mock import MagicMock, patch import numpy as np +if TYPE_CHECKING: + from collections.abc import Callable + def _spy_vc() -> tuple[object, list[tuple[object, float]]]: """VideoClip spy capturing make_frame closures.""" captured: list[tuple[object, float]] = [] - def spy(make_frame=None, duration=None, **_kw: object) -> MagicMock: + def spy( + make_frame: Callable[[float], np.ndarray] | None = None, + duration: float | None = None, + **_kw: object, + ) -> MagicMock: if callable(make_frame): captured.append((make_frame, duration or 1.0)) clip = MagicMock() diff --git a/python_pkg/praca_magisterska_video/tests/test_q24_rcnn_part2.py b/python_pkg/praca_magisterska_video/tests/test_q24_rcnn_part2.py index 9577995..18a5c62 100644 --- a/python_pkg/praca_magisterska_video/tests/test_q24_rcnn_part2.py +++ b/python_pkg/praca_magisterska_video/tests/test_q24_rcnn_part2.py @@ -2,16 +2,24 @@ from __future__ import annotations +from typing import TYPE_CHECKING from unittest.mock import MagicMock, patch import numpy as np +if TYPE_CHECKING: + from collections.abc import Callable + def _spy_vc() -> tuple[object, list[tuple[object, float]]]: """VideoClip spy capturing make_frame closures.""" captured: list[tuple[object, float]] = [] - def spy(make_frame=None, duration=None, **_kw: object) -> MagicMock: + def spy( + make_frame: Callable[[float], np.ndarray] | None = None, + duration: float | None = None, + **_kw: object, + ) -> MagicMock: if callable(make_frame): captured.append((make_frame, duration or 1.0)) clip = MagicMock() diff --git a/python_pkg/praca_magisterska_video/tests/test_q24_rpn_yolo_part2.py b/python_pkg/praca_magisterska_video/tests/test_q24_rpn_yolo_part2.py index 2a31db5..b182f05 100644 --- a/python_pkg/praca_magisterska_video/tests/test_q24_rpn_yolo_part2.py +++ b/python_pkg/praca_magisterska_video/tests/test_q24_rpn_yolo_part2.py @@ -2,16 +2,24 @@ from __future__ import annotations +from typing import TYPE_CHECKING from unittest.mock import MagicMock, patch import numpy as np +if TYPE_CHECKING: + from collections.abc import Callable + def _spy_vc() -> tuple[object, list[tuple[object, float]]]: """VideoClip spy capturing make_frame closures.""" captured: list[tuple[object, float]] = [] - def spy(make_frame=None, duration=None, **_kw: object) -> MagicMock: + def spy( + make_frame: Callable[[float], np.ndarray] | None = None, + duration: float | None = None, + **_kw: object, + ) -> MagicMock: if callable(make_frame): captured.append((make_frame, duration or 1.0)) clip = MagicMock() diff --git a/python_pkg/praca_magisterska_video/tests/test_q24_yolo_arch_detr_part2.py b/python_pkg/praca_magisterska_video/tests/test_q24_yolo_arch_detr_part2.py index af3a827..6adb0f2 100644 --- a/python_pkg/praca_magisterska_video/tests/test_q24_yolo_arch_detr_part2.py +++ b/python_pkg/praca_magisterska_video/tests/test_q24_yolo_arch_detr_part2.py @@ -2,16 +2,24 @@ from __future__ import annotations +from typing import TYPE_CHECKING from unittest.mock import MagicMock, patch import numpy as np +if TYPE_CHECKING: + from collections.abc import Callable + def _spy_vc() -> tuple[object, list[tuple[object, float]]]: """VideoClip spy capturing make_frame closures.""" captured: list[tuple[object, float]] = [] - def spy(make_frame=None, duration=None, **_kw: object) -> MagicMock: + def spy( + make_frame: Callable[[float], np.ndarray] | None = None, + duration: float | None = None, + **_kw: object, + ) -> MagicMock: if callable(make_frame): captured.append((make_frame, duration or 1.0)) clip = MagicMock() diff --git a/python_pkg/praca_magisterska_video/tests/test_visualize_q02_part2.py b/python_pkg/praca_magisterska_video/tests/test_visualize_q02_part2.py index 7bce2aa..fb74c9f 100644 --- a/python_pkg/praca_magisterska_video/tests/test_visualize_q02_part2.py +++ b/python_pkg/praca_magisterska_video/tests/test_visualize_q02_part2.py @@ -60,7 +60,7 @@ def test_make_frame_closure_returns_ndarray() -> None: captured: list[object] = [] - def capturing_video_clip(make_frame: object = None, **kw: object) -> MagicMock: + def capturing_video_clip(make_frame: object = None, **_kw: object) -> MagicMock: captured.append(make_frame) clip = MagicMock() clip.with_fps.return_value = clip diff --git a/python_pkg/puzzle_solver/main.py b/python_pkg/puzzle_solver/main.py index 6e7b08e..c8491e1 100644 --- a/python_pkg/puzzle_solver/main.py +++ b/python_pkg/puzzle_solver/main.py @@ -18,6 +18,7 @@ Usage from __future__ import annotations import argparse +from collections import Counter import json from pathlib import Path import sys @@ -70,8 +71,6 @@ def cmd_debug(args: argparse.Namespace) -> None: data = parse_image(args.image, threshold=args.threshold) out = args.output or args.image.rsplit(".", 1)[0] + "_debug.png" draw_debug(args.image, data, out) - from collections import Counter - counts = Counter(sq["type"] for sq in data["squares"]) for _t, _n in counts.most_common(): pass diff --git a/python_pkg/puzzle_solver/tests/conftest.py b/python_pkg/puzzle_solver/tests/conftest.py new file mode 100644 index 0000000..581ba47 --- /dev/null +++ b/python_pkg/puzzle_solver/tests/conftest.py @@ -0,0 +1,9 @@ +"""Mock cv2/numpy if not installed before puzzle_solver tests.""" + +from __future__ import annotations + +import sys +from unittest.mock import MagicMock + +sys.modules.setdefault("cv2", MagicMock()) +sys.modules.setdefault("numpy", MagicMock()) diff --git a/python_pkg/puzzle_solver/tests/test_main.py b/python_pkg/puzzle_solver/tests/test_main.py index f7c3274..9b5d383 100644 --- a/python_pkg/puzzle_solver/tests/test_main.py +++ b/python_pkg/puzzle_solver/tests/test_main.py @@ -3,16 +3,11 @@ from __future__ import annotations import json -import sys from typing import Any from unittest.mock import MagicMock, mock_open, patch import pytest -# Ensure cv2 and numpy are available as mocks before importing main -sys.modules.setdefault("cv2", MagicMock()) -sys.modules.setdefault("numpy", MagicMock()) - from python_pkg.puzzle_solver.main import ( cmd_debug, cmd_parse, diff --git a/python_pkg/puzzle_solver/tests/test_parse_image.py b/python_pkg/puzzle_solver/tests/test_parse_image.py index 571507f..045e6b9 100644 --- a/python_pkg/puzzle_solver/tests/test_parse_image.py +++ b/python_pkg/puzzle_solver/tests/test_parse_image.py @@ -2,17 +2,10 @@ from __future__ import annotations -import sys from unittest.mock import MagicMock, mock_open, patch import pytest -# Install mock modules before any parse_image imports -_cv2_mock = MagicMock() -_np_mock = MagicMock() -sys.modules.setdefault("cv2", _cv2_mock) -sys.modules.setdefault("numpy", _np_mock) - from python_pkg.puzzle_solver.parse_image import ( _classify_by_fill, _classify_interior_feature, diff --git a/python_pkg/puzzle_solver/tests/test_parse_image_part2.py b/python_pkg/puzzle_solver/tests/test_parse_image_part2.py index b0990fa..9939de7 100644 --- a/python_pkg/puzzle_solver/tests/test_parse_image_part2.py +++ b/python_pkg/puzzle_solver/tests/test_parse_image_part2.py @@ -2,16 +2,11 @@ from __future__ import annotations -import sys from typing import Any from unittest.mock import MagicMock, patch import numpy as np -# Install mock modules before any parse_image imports -sys.modules.setdefault("cv2", MagicMock()) -sys.modules.setdefault("numpy", MagicMock()) - from python_pkg.puzzle_solver.parse_image import ( _assign_teleporter_and_kl_groups, _build_output, diff --git a/python_pkg/puzzle_solver/tests/test_parse_image_part3.py b/python_pkg/puzzle_solver/tests/test_parse_image_part3.py index 69135a7..d6463f5 100644 --- a/python_pkg/puzzle_solver/tests/test_parse_image_part3.py +++ b/python_pkg/puzzle_solver/tests/test_parse_image_part3.py @@ -2,14 +2,9 @@ from __future__ import annotations -import sys from typing import Any from unittest.mock import MagicMock, patch -# Install mock modules before any parse_image imports -sys.modules.setdefault("cv2", MagicMock()) -sys.modules.setdefault("numpy", MagicMock()) - from python_pkg.puzzle_solver.parse_image import ( _assign_teleporter_and_kl_groups, draw_debug, diff --git a/python_pkg/repo_explorer/tests/test_discovery.py b/python_pkg/repo_explorer/tests/test_discovery.py index 92cad2a..ce479fc 100644 --- a/python_pkg/repo_explorer/tests/test_discovery.py +++ b/python_pkg/repo_explorer/tests/test_discovery.py @@ -3,7 +3,6 @@ from __future__ import annotations from pathlib import Path, PurePosixPath -from typing import Any from unittest.mock import MagicMock, patch from python_pkg.repo_explorer._discovery import ( @@ -173,7 +172,7 @@ class TestGetDescription: readme.exists.return_value = True readme.read_text.return_value = "# My Project\nDetails here" - def truediv(_self: Any, name: str) -> MagicMock: + def truediv(_self: object, name: str) -> MagicMock: if name == "README.md": return readme m = MagicMock(spec=Path) @@ -187,7 +186,7 @@ class TestGetDescription: def test_readme_txt(self) -> None: mock_path = MagicMock(spec=Path) - def truediv(_self: Any, name: str) -> MagicMock: + def truediv(_self: object, name: str) -> MagicMock: m = MagicMock(spec=Path) if name == "README.txt": m.exists.return_value = True @@ -203,7 +202,7 @@ class TestGetDescription: def test_readme_lower(self) -> None: mock_path = MagicMock(spec=Path) - def truediv(_self: Any, name: str) -> MagicMock: + def truediv(_self: object, name: str) -> MagicMock: m = MagicMock(spec=Path) if name == "readme.md": m.exists.return_value = True @@ -220,7 +219,7 @@ class TestGetDescription: """README exists but all lines strip to empty.""" mock_path = MagicMock(spec=Path) - def truediv(_self: Any, name: str) -> MagicMock: + def truediv(_self: object, name: str) -> MagicMock: m = MagicMock(spec=Path) if name == "README.md": m.exists.return_value = True @@ -243,7 +242,7 @@ class TestGetDescription: run_sh = MagicMock(spec=Path) run_sh.exists.return_value = True - def truediv(_self: Any, name: str) -> MagicMock: + def truediv(_self: object, name: str) -> MagicMock: if name == "run.sh": return run_sh m = MagicMock(spec=Path) @@ -261,7 +260,7 @@ class TestGetDescription: run_sh = MagicMock(spec=Path) run_sh.exists.return_value = True - def truediv(_self: Any, name: str) -> MagicMock: + def truediv(_self: object, name: str) -> MagicMock: if name == "run.sh": return run_sh m = MagicMock(spec=Path) @@ -275,7 +274,7 @@ class TestGetDescription: def test_no_readme_no_run_sh(self) -> None: mock_path = MagicMock(spec=Path) - def truediv(_self: Any, _name: str) -> MagicMock: + def truediv(_self: object, _name: str) -> MagicMock: m = MagicMock(spec=Path) m.exists.return_value = False return m diff --git a/python_pkg/repo_explorer/tests/test_execution.py b/python_pkg/repo_explorer/tests/test_execution.py index 8779656..7f2f4e9 100644 --- a/python_pkg/repo_explorer/tests/test_execution.py +++ b/python_pkg/repo_explorer/tests/test_execution.py @@ -10,6 +10,7 @@ from unittest.mock import MagicMock, patch from python_pkg.repo_explorer._execution import ExecutionMixin if TYPE_CHECKING: + from pathlib import Path import subprocess # ── Protocol stub coverage ─────────────────────────────────────────── @@ -45,7 +46,7 @@ class StubExecution(ExecutionMixin): self._path: Any = None self._after_calls: list[tuple[Any, ...]] = [] - def _selected_path(self) -> Any: + def _selected_path(self) -> Path | None: return self._path def after(self, ms: int, *args: object) -> str: @@ -338,7 +339,11 @@ class TestReadPty: read_calls = [0] - def fake_select(rlist: list[int], *_a: Any, **_kw: Any) -> Any: + def fake_select( + _rlist: list[int], + *_a: object, + **_kw: object, + ) -> tuple[list[int], list[object], list[object]]: read_calls[0] += 1 if read_calls[0] == 1: # First call: return data (no newline → stays in buf) @@ -393,7 +398,11 @@ class TestReadPty: call_count = [0] - def fake_select(rlist: list[int], *_a: Any, **_kw: Any) -> Any: + def fake_select( + _rlist: list[int], + *_a: object, + **_kw: object, + ) -> tuple[list[int], list[object], list[object]]: call_count[0] += 1 if call_count[0] == 1: return ([10], [], []) diff --git a/python_pkg/repo_explorer/tests/test_execution_part2.py b/python_pkg/repo_explorer/tests/test_execution_part2.py index 8ffacf0..5b81026 100644 --- a/python_pkg/repo_explorer/tests/test_execution_part2.py +++ b/python_pkg/repo_explorer/tests/test_execution_part2.py @@ -10,6 +10,7 @@ from unittest.mock import MagicMock from python_pkg.repo_explorer._execution import ExecutionMixin if TYPE_CHECKING: + from pathlib import Path import subprocess @@ -31,7 +32,7 @@ class StubExecution(ExecutionMixin): self._path: Any = None self._after_calls: list[tuple[Any, ...]] = [] - def _selected_path(self) -> Any: + def _selected_path(self) -> Path | None: return self._path def after(self, ms: int, *args: object) -> str: diff --git a/python_pkg/repo_explorer/tests/test_repo_explorer.py b/python_pkg/repo_explorer/tests/test_repo_explorer.py index c40b00d..3a338f5 100644 --- a/python_pkg/repo_explorer/tests/test_repo_explorer.py +++ b/python_pkg/repo_explorer/tests/test_repo_explorer.py @@ -4,13 +4,12 @@ from __future__ import annotations from pathlib import Path, PurePosixPath import tkinter as tk -from typing import Any from unittest.mock import MagicMock, patch # ── Helper to create a RepoExplorer without a real display ─────────── -def _make_explorer(**overrides: Any) -> Any: +def _make_explorer(**overrides: object) -> object: """Build a RepoExplorer instance without a real Tk display. Mocks tk.Tk.__init__ and all GUI construction so no X server is needed. @@ -108,83 +107,73 @@ class TestBuildStyle: class TestBuildUI: - @patch("python_pkg.repo_explorer.repo_explorer.ttk.Scrollbar") - @patch("python_pkg.repo_explorer.repo_explorer.ttk.Treeview") - @patch("python_pkg.repo_explorer.repo_explorer.font.Font") - @patch("python_pkg.repo_explorer.repo_explorer.ttk.Button") - @patch("python_pkg.repo_explorer.repo_explorer.ttk.Entry") - @patch("python_pkg.repo_explorer.repo_explorer.ttk.Separator") - @patch("python_pkg.repo_explorer.repo_explorer.ttk.Label") - @patch("python_pkg.repo_explorer.repo_explorer.ttk.Frame") - @patch("python_pkg.repo_explorer.repo_explorer.ttk.PanedWindow") - @patch("python_pkg.repo_explorer.repo_explorer.tk.Text") - @patch("python_pkg.repo_explorer.repo_explorer.tk.StringVar") - def test_build_ui_with_terminal( - self, - mock_stringvar: MagicMock, - mock_text: MagicMock, - mock_paned: MagicMock, - mock_frame: MagicMock, - mock_label: MagicMock, - mock_sep: MagicMock, - mock_entry: MagicMock, - mock_button: MagicMock, - mock_font: MagicMock, - mock_treeview: MagicMock, - mock_scrollbar: MagicMock, - ) -> None: - app = _make_explorer() - mock_sv = MagicMock() - mock_stringvar.return_value = mock_sv - paned = MagicMock() - mock_paned.return_value = paned + def test_build_ui_with_terminal(self) -> None: + with ( + patch( + "python_pkg.repo_explorer.repo_explorer.tk.StringVar" + ) as mock_stringvar, + patch("python_pkg.repo_explorer.repo_explorer.tk.Text") as mock_text, + patch( + "python_pkg.repo_explorer.repo_explorer.ttk.PanedWindow" + ) as mock_paned, + patch("python_pkg.repo_explorer.repo_explorer.ttk.Frame"), + patch("python_pkg.repo_explorer.repo_explorer.ttk.Label"), + patch("python_pkg.repo_explorer.repo_explorer.ttk.Separator"), + patch("python_pkg.repo_explorer.repo_explorer.ttk.Entry"), + patch("python_pkg.repo_explorer.repo_explorer.ttk.Button"), + patch("python_pkg.repo_explorer.repo_explorer.font.Font"), + patch( + "python_pkg.repo_explorer.repo_explorer.ttk.Treeview" + ) as mock_treeview, + patch("python_pkg.repo_explorer.repo_explorer.ttk.Scrollbar"), + ): + app = _make_explorer() + mock_sv = MagicMock() + mock_stringvar.return_value = mock_sv + paned = MagicMock() + mock_paned.return_value = paned - tree = MagicMock() - mock_treeview.return_value = tree - text = MagicMock() - mock_text.return_value = text + tree = MagicMock() + mock_treeview.return_value = tree + text = MagicMock() + mock_text.return_value = text - app.pack = MagicMock() - app._build_ui() + app.pack = MagicMock() + app._build_ui() - @patch("python_pkg.repo_explorer.repo_explorer.ttk.Scrollbar") - @patch("python_pkg.repo_explorer.repo_explorer.ttk.Treeview") - @patch("python_pkg.repo_explorer.repo_explorer.font.Font") - @patch("python_pkg.repo_explorer.repo_explorer.ttk.Button") - @patch("python_pkg.repo_explorer.repo_explorer.ttk.Entry") - @patch("python_pkg.repo_explorer.repo_explorer.ttk.Separator") - @patch("python_pkg.repo_explorer.repo_explorer.ttk.Label") - @patch("python_pkg.repo_explorer.repo_explorer.ttk.Frame") - @patch("python_pkg.repo_explorer.repo_explorer.ttk.PanedWindow") - @patch("python_pkg.repo_explorer.repo_explorer.tk.Text") - @patch("python_pkg.repo_explorer.repo_explorer.tk.StringVar") - def test_build_ui_no_terminal( - self, - mock_stringvar: MagicMock, - mock_text: MagicMock, - mock_paned: MagicMock, - mock_frame: MagicMock, - mock_label: MagicMock, - mock_sep: MagicMock, - mock_entry: MagicMock, - mock_button: MagicMock, - mock_font: MagicMock, - mock_treeview: MagicMock, - mock_scrollbar: MagicMock, - ) -> None: - app = _make_explorer(terminal_args=[]) - mock_sv = MagicMock() - mock_stringvar.return_value = mock_sv - paned = MagicMock() - mock_paned.return_value = paned + def test_build_ui_no_terminal(self) -> None: + with ( + patch( + "python_pkg.repo_explorer.repo_explorer.tk.StringVar" + ) as mock_stringvar, + patch("python_pkg.repo_explorer.repo_explorer.tk.Text") as mock_text, + patch( + "python_pkg.repo_explorer.repo_explorer.ttk.PanedWindow" + ) as mock_paned, + patch("python_pkg.repo_explorer.repo_explorer.ttk.Frame"), + patch("python_pkg.repo_explorer.repo_explorer.ttk.Label"), + patch("python_pkg.repo_explorer.repo_explorer.ttk.Separator"), + patch("python_pkg.repo_explorer.repo_explorer.ttk.Entry"), + patch("python_pkg.repo_explorer.repo_explorer.ttk.Button"), + patch("python_pkg.repo_explorer.repo_explorer.font.Font"), + patch( + "python_pkg.repo_explorer.repo_explorer.ttk.Treeview" + ) as mock_treeview, + patch("python_pkg.repo_explorer.repo_explorer.ttk.Scrollbar"), + ): + app = _make_explorer(terminal_args=[]) + mock_sv = MagicMock() + mock_stringvar.return_value = mock_sv + paned = MagicMock() + mock_paned.return_value = paned - tree = MagicMock() - mock_treeview.return_value = tree - text = MagicMock() - mock_text.return_value = text + tree = MagicMock() + mock_treeview.return_value = tree + text = MagicMock() + mock_text.return_value = text - app.pack = MagicMock() - app._build_ui() + app.pack = MagicMock() + app._build_ui() # ── _load_projects ─────────────────────────────────────────────────── diff --git a/python_pkg/screen_locker/screen_lock.py b/python_pkg/screen_locker/screen_lock.py index ce06b47..2beb690 100755 --- a/python_pkg/screen_locker/screen_lock.py +++ b/python_pkg/screen_locker/screen_lock.py @@ -92,10 +92,10 @@ class ScreenLocker( """Configure the window for fullscreen lock.""" screen_w = self.root.winfo_screenwidth() screen_h = self.root.winfo_screenheight() - self.root.overrideredirect(True) + self.root.overrideredirect(boolean=True) self.root.geometry(f"{screen_w}x{screen_h}+0+0") - self.root.attributes("-fullscreen", True) - self.root.attributes("-topmost", True) + self.root.attributes(fullscreen=True) + self.root.attributes(topmost=True) self.root.configure(bg="#1a1a1a", cursor="arrow") def _setup_demo_close_button(self) -> None: diff --git a/python_pkg/screen_locker/tests/test_adb_and_phone.py b/python_pkg/screen_locker/tests/test_adb_and_phone.py index be60544..d0ec67c 100644 --- a/python_pkg/screen_locker/tests/test_adb_and_phone.py +++ b/python_pkg/screen_locker/tests/test_adb_and_phone.py @@ -21,7 +21,7 @@ class TestRunAdb: def test_run_adb_success( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test successful ADB command.""" @@ -40,7 +40,7 @@ class TestRunAdb: def test_run_adb_failure( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test failed ADB command.""" @@ -57,7 +57,7 @@ class TestRunAdb: def test_run_adb_not_found( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test ADB binary not found.""" @@ -74,7 +74,7 @@ class TestRunAdb: def test_run_adb_oserror( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test ADB OSError.""" @@ -91,7 +91,7 @@ class TestRunAdb: def test_run_adb_timeout( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test ADB command timeout.""" @@ -112,7 +112,7 @@ class TestAdbShell: def test_adb_shell_no_root( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test ADB shell without root.""" @@ -134,7 +134,7 @@ class TestAdbShell: def test_adb_shell_with_root( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test ADB shell with root.""" @@ -161,7 +161,7 @@ class TestIsPhoneConnected: def test_phone_connected( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test phone detected as connected.""" @@ -182,7 +182,7 @@ class TestIsPhoneConnected: def test_phone_not_connected( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test no phone connected.""" @@ -207,7 +207,7 @@ class TestIsPhoneConnected: def test_phone_offline( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test phone connected but offline.""" @@ -235,7 +235,7 @@ class TestIsPhoneConnected: def test_adb_command_fails( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test ADB command failure.""" @@ -264,7 +264,7 @@ class TestFindHealthConnectDb: def test_db_pulled_successfully( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test StrongLifts DB pulled from device.""" @@ -295,7 +295,7 @@ class TestFindHealthConnectDb: def test_db_cat_fails( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test returns None when cat command fails.""" @@ -313,7 +313,7 @@ class TestFindHealthConnectDb: def test_db_pull_fails( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test returns None when adb pull fails.""" @@ -338,7 +338,7 @@ class TestFindHealthConnectDb: def test_db_uses_correct_remote_path( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test uses the correct StrongLifts DB remote path.""" @@ -370,7 +370,7 @@ class TestCountTodayWorkouts: def test_workouts_found_today( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test workouts found today.""" @@ -395,7 +395,7 @@ class TestCountTodayWorkouts: def test_no_workouts_today( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test no workouts today.""" @@ -420,7 +420,7 @@ class TestCountTodayWorkouts: def test_invalid_db_returns_zero( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test returns 0 for invalid database file.""" @@ -433,7 +433,7 @@ class TestCountTodayWorkouts: def test_missing_table_returns_zero( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test returns 0 when workouts table doesn't exist.""" @@ -449,7 +449,7 @@ class TestCountTodayWorkouts: def test_multiple_workouts_today( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test counts multiple workouts today correctly.""" diff --git a/python_pkg/screen_locker/tests/test_init_and_log.py b/python_pkg/screen_locker/tests/test_init_and_log.py index ebaae89..f7ad5dc 100644 --- a/python_pkg/screen_locker/tests/test_init_and_log.py +++ b/python_pkg/screen_locker/tests/test_init_and_log.py @@ -105,7 +105,7 @@ class TestHasLoggedToday: def test_no_log_file( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test when log file doesn't exist.""" @@ -118,7 +118,7 @@ class TestHasLoggedToday: def test_empty_log_file( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test when log file is empty/invalid JSON.""" @@ -132,7 +132,7 @@ class TestHasLoggedToday: def test_invalid_json( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test when log file contains invalid JSON.""" @@ -146,7 +146,7 @@ class TestHasLoggedToday: def test_today_logged( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test when today's workout is logged.""" @@ -161,7 +161,7 @@ class TestHasLoggedToday: def test_other_day_logged( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test when only other days are logged.""" @@ -179,7 +179,7 @@ class TestSaveWorkoutLog: def test_save_to_new_file( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test saving to a new log file.""" @@ -199,7 +199,7 @@ class TestSaveWorkoutLog: def test_save_to_existing_file( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test saving appends to existing log file.""" @@ -220,7 +220,7 @@ class TestSaveWorkoutLog: def test_save_with_corrupted_existing_file( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test saving when existing file is corrupted.""" @@ -240,7 +240,7 @@ class TestSaveWorkoutLog: def test_save_with_write_error( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test saving handles write errors gracefully.""" @@ -259,7 +259,7 @@ class TestShowError: def test_show_error_displays_message( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test show_error clears container and displays error.""" @@ -277,7 +277,7 @@ class TestRun: def test_run_starts_mainloop( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test run starts the tkinter mainloop.""" @@ -294,7 +294,7 @@ class TestMainEntry: def test_main_demo_mode_default( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test main defaults to demo mode.""" @@ -305,7 +305,7 @@ class TestMainEntry: def test_main_production_mode_flag( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test main with --production flag.""" @@ -320,7 +320,7 @@ class TestAdjustShutdownTimeLater: def test_adjust_shutdown_time_later_success( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test _adjust_shutdown_time_later adds hours successfully.""" @@ -340,7 +340,7 @@ class TestAdjustShutdownTimeLater: def test_adjust_shutdown_time_later_caps_at_23( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test _adjust_shutdown_time_later caps hours at 23.""" @@ -361,7 +361,7 @@ class TestAdjustShutdownTimeLater: def test_adjust_shutdown_time_later_no_config( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test _adjust_shutdown_time_later returns False if config missing.""" @@ -377,7 +377,7 @@ class TestAdjustShutdownTimeLater: def test_adjust_shutdown_time_later_oserror( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test _adjust_shutdown_time_later handles OSError.""" @@ -397,7 +397,7 @@ class TestGrabInput: """Tests for _grab_input method.""" def test_production_global_grab_tcl_error( - self, mock_tk: MagicMock, _mock_sys_exit: MagicMock, tmp_path: Path + self, mock_tk: MagicMock, mock_sys_exit: MagicMock, tmp_path: Path ) -> None: """Test production mode falls back when global grab fails.""" mock_tk.Tk.return_value.grab_set_global.side_effect = tk.TclError("grab failed") diff --git a/python_pkg/screen_locker/tests/test_phone_check_unlock.py b/python_pkg/screen_locker/tests/test_phone_check_unlock.py index dda7119..9caedc8 100644 --- a/python_pkg/screen_locker/tests/test_phone_check_unlock.py +++ b/python_pkg/screen_locker/tests/test_phone_check_unlock.py @@ -21,7 +21,7 @@ class TestVerifyPhoneWorkout: def test_verified( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test workout verified on phone.""" @@ -56,7 +56,7 @@ class TestVerifyPhoneWorkout: def test_not_verified( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test no workout found on phone.""" @@ -91,7 +91,7 @@ class TestVerifyPhoneWorkout: def test_no_phone( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test no phone connected.""" @@ -111,7 +111,7 @@ class TestVerifyPhoneWorkout: def test_error_no_db( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test error when StrongLifts DB cannot be pulled.""" @@ -143,7 +143,7 @@ class TestStartPhoneCheck: def test_start_phone_check_shows_checking_screen( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test _start_phone_check shows checking message and starts check.""" @@ -167,7 +167,7 @@ class TestStartPhoneCheck: def test_handle_startup_verified_unlocks_directly( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test verified result shows success screen then unlocks via after().""" @@ -185,7 +185,7 @@ class TestStartPhoneCheck: def test_handle_startup_not_verified_shows_block( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test not_verified result shows blocking screen with buttons.""" @@ -200,7 +200,7 @@ class TestStartPhoneCheck: def test_handle_startup_no_phone_shows_penalty( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test no_phone result triggers penalty with ask_workout_done as callback.""" @@ -216,7 +216,7 @@ class TestStartPhoneCheck: def test_handle_startup_error_shows_penalty( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test error result triggers penalty with ask_workout_done as callback.""" @@ -232,7 +232,7 @@ class TestStartPhoneCheck: def test_poll_phone_check_schedules_retry_when_pending( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test _poll_phone_check reschedules itself when future is not done.""" @@ -249,7 +249,7 @@ class TestStartPhoneCheck: def test_poll_phone_check_routes_when_done( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test _poll_phone_check calls result handler when future is done.""" @@ -273,7 +273,7 @@ class TestAttemptUnlock: def test_attempt_unlock_calls_unlock_screen( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test _attempt_unlock calls unlock_screen directly.""" @@ -293,7 +293,7 @@ class TestShowPhonePenalty: def test_show_phone_penalty_demo_delay( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test demo mode uses short penalty delay.""" @@ -308,7 +308,7 @@ class TestShowPhonePenalty: def test_show_phone_penalty_production_delay( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test production mode uses long penalty delay.""" @@ -322,7 +322,7 @@ class TestShowPhonePenalty: def test_update_phone_penalty_countdown( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test phone penalty countdown decrements.""" @@ -339,7 +339,7 @@ class TestShowPhonePenalty: def test_update_phone_penalty_at_zero( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test phone penalty unlocks when timer reaches zero.""" @@ -362,7 +362,7 @@ class TestUnlockScreenShutdownAdjustment: def test_unlock_screen_adjusts_for_running( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test unlock_screen adjusts shutdown for running workout.""" @@ -380,7 +380,7 @@ class TestUnlockScreenShutdownAdjustment: def test_unlock_screen_adjusts_for_strength( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test unlock_screen adjusts shutdown for strength workout.""" @@ -398,7 +398,7 @@ class TestUnlockScreenShutdownAdjustment: def test_unlock_screen_adjusts_for_phone_verified( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test unlock_screen adjusts shutdown for phone-verified workout.""" @@ -416,7 +416,7 @@ class TestUnlockScreenShutdownAdjustment: def test_unlock_screen_skips_adjustment_for_sick_day( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test unlock_screen does not adjust for sick day.""" @@ -434,7 +434,7 @@ class TestUnlockScreenShutdownAdjustment: def test_unlock_screen_skips_adjustment_no_type( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test unlock_screen does not adjust when no workout type.""" @@ -452,7 +452,7 @@ class TestUnlockScreenShutdownAdjustment: def test_unlock_screen_handles_adjustment_failure( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test unlock_screen continues when adjustment fails.""" diff --git a/python_pkg/screen_locker/tests/test_phone_verification_part2.py b/python_pkg/screen_locker/tests/test_phone_verification_part2.py index 6150864..5ee4a5b 100644 --- a/python_pkg/screen_locker/tests/test_phone_verification_part2.py +++ b/python_pkg/screen_locker/tests/test_phone_verification_part2.py @@ -17,7 +17,7 @@ class TestGetWirelessSerial: def test_returns_wireless_serial( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test returns ip:port serial for a wireless device.""" @@ -30,7 +30,7 @@ class TestGetWirelessSerial: def test_returns_none_when_adb_fails( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test returns None when adb devices fails.""" @@ -42,7 +42,7 @@ class TestGetWirelessSerial: def test_returns_none_when_no_wireless_device( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test returns None when only USB devices are connected.""" @@ -55,7 +55,7 @@ class TestGetWirelessSerial: def test_skips_offline_wireless_device( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test skips offline wireless devices.""" @@ -72,7 +72,7 @@ class TestTryAdbConnect: def test_successful_connect( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test successful ADB connect.""" @@ -86,7 +86,7 @@ class TestTryAdbConnect: def test_failed_connect_unable( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test connect failure with 'unable' in output.""" @@ -100,7 +100,7 @@ class TestTryAdbConnect: def test_failed_connect_with_failed( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test connect failure with 'failed' in output.""" @@ -116,7 +116,7 @@ class TestTryAdbConnect: def test_no_connected_in_output( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test connect failure when 'connected' not in output.""" @@ -134,7 +134,7 @@ class TestGetLocalSubnetPrefix: def test_returns_prefix( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test returns first three octets of local IP.""" @@ -153,7 +153,7 @@ class TestGetLocalSubnetPrefix: def test_returns_none_on_oserror( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test returns None when socket raises OSError.""" @@ -172,7 +172,7 @@ class TestTryWirelessReconnect: def test_returns_false_when_no_prefix( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test returns False when subnet prefix can't be determined.""" @@ -184,7 +184,7 @@ class TestTryWirelessReconnect: def test_returns_true_when_probe_succeeds( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test returns True when a probe finds the phone.""" @@ -207,7 +207,7 @@ class TestTryWirelessReconnect: def test_returns_false_when_no_probe_succeeds( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test returns False when no probe finds the phone.""" @@ -225,7 +225,7 @@ class TestTryWirelessReconnect: def test_probe_connect_succeeds_but_no_device( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test probe passes socket but adb_connect succeeds without device.""" @@ -248,7 +248,7 @@ class TestTryWirelessReconnect: def test_probe_adb_connect_fails( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test probe where socket connects but adb connect fails.""" diff --git a/python_pkg/screen_locker/tests/test_shutdown_part2.py b/python_pkg/screen_locker/tests/test_shutdown_part2.py index f687d1e..28822a8 100644 --- a/python_pkg/screen_locker/tests/test_shutdown_part2.py +++ b/python_pkg/screen_locker/tests/test_shutdown_part2.py @@ -18,7 +18,7 @@ class TestApplyEarlierShutdown: def test_returns_false_when_no_config( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test returns False when config can't be read.""" @@ -29,7 +29,7 @@ class TestApplyEarlierShutdown: def test_returns_false_when_save_state_fails( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test returns False when saving state fails.""" @@ -43,7 +43,7 @@ class TestApplyEarlierShutdown: def test_success_applies_earlier_hours( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test successful application of earlier shutdown hours.""" @@ -62,7 +62,7 @@ class TestApplyEarlierShutdown: def test_clamps_to_minimum_18( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test hours are clamped to minimum of 18.""" @@ -84,7 +84,7 @@ class TestAdjustShutdownTimeEarlier: def test_returns_false_when_sick_mode_used_today( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test returns False when sick mode already used today.""" @@ -98,7 +98,7 @@ class TestAdjustShutdownTimeEarlier: def test_success( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test successful adjustment.""" @@ -113,7 +113,7 @@ class TestAdjustShutdownTimeEarlier: def test_handles_oserror( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test handles OSError during apply.""" @@ -132,7 +132,7 @@ class TestAdjustShutdownTimeEarlier: def test_handles_value_error( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test handles ValueError during apply.""" @@ -155,7 +155,7 @@ class TestAdjustShutdownTimeLater: def test_returns_false_when_no_config( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test returns False when config is missing.""" @@ -166,7 +166,7 @@ class TestAdjustShutdownTimeLater: def test_success_applies_later_hours( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test successful later adjustment with restore flag.""" @@ -184,7 +184,7 @@ class TestAdjustShutdownTimeLater: def test_clamps_to_max_23( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test hours are clamped to maximum of 23.""" @@ -201,7 +201,7 @@ class TestAdjustShutdownTimeLater: def test_handles_oserror( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test handles OSError.""" @@ -220,7 +220,7 @@ class TestSickModeUsedToday: def test_returns_false_when_no_file( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test returns False when state file doesn't exist.""" @@ -236,7 +236,7 @@ class TestSickModeUsedToday: def test_returns_true_when_used_today( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test returns True when state matches today.""" @@ -255,7 +255,7 @@ class TestSickModeUsedToday: def test_returns_false_when_different_date( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test returns False when state is from different date.""" @@ -271,7 +271,7 @@ class TestSickModeUsedToday: def test_returns_false_on_json_error( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test returns False on JSONDecodeError.""" @@ -291,7 +291,7 @@ class TestSaveSickDayState: def test_saves_state_successfully( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test saves state file with correct content.""" @@ -311,7 +311,7 @@ class TestSaveSickDayState: def test_returns_false_on_oserror( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test returns False when write fails.""" @@ -332,7 +332,7 @@ class TestLoadSickDayState: def test_loads_valid_state( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test loads state with all fields present.""" @@ -357,7 +357,7 @@ class TestLoadSickDayState: def test_returns_none_when_fields_missing( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test returns None when required fields are missing.""" @@ -378,7 +378,7 @@ class TestWriteRestoredConfig: def test_restores_config_and_removes_state( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test restores config values and deletes state file.""" @@ -402,7 +402,7 @@ class TestWriteRestoredConfig: def test_still_removes_state_when_config_read_fails( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test removes state file even when config read returns None.""" diff --git a/python_pkg/screen_locker/tests/test_shutdown_part3.py b/python_pkg/screen_locker/tests/test_shutdown_part3.py index f35626d..7ec85c7 100644 --- a/python_pkg/screen_locker/tests/test_shutdown_part3.py +++ b/python_pkg/screen_locker/tests/test_shutdown_part3.py @@ -20,7 +20,7 @@ class TestRestoreOriginalConfigIfNeeded: def test_no_state_file_does_nothing( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test does nothing when no state file exists.""" @@ -36,7 +36,7 @@ class TestRestoreOriginalConfigIfNeeded: def test_restores_when_state_from_previous_day( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test restores config when state date differs from today.""" @@ -64,7 +64,7 @@ class TestRestoreOriginalConfigIfNeeded: def test_does_not_restore_when_state_from_today( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test does not restore when state date matches today.""" @@ -95,7 +95,7 @@ class TestRestoreOriginalConfigIfNeeded: def test_returns_when_loaded_state_is_none( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test returns early when loaded state is None.""" @@ -115,7 +115,7 @@ class TestRestoreOriginalConfigIfNeeded: def test_handles_oserror( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test handles OSError when loading state.""" @@ -132,7 +132,7 @@ class TestRestoreOriginalConfigIfNeeded: def test_handles_json_decode_error( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test handles JSONDecodeError when loading state.""" @@ -152,7 +152,7 @@ class TestReadShutdownConfig: def test_returns_none_when_file_missing( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test returns None when config file doesn't exist.""" @@ -168,7 +168,7 @@ class TestReadShutdownConfig: def test_reads_valid_config( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test reads all three config values from file.""" @@ -185,7 +185,7 @@ class TestReadShutdownConfig: def test_returns_none_when_values_missing( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test returns None when config has missing keys.""" @@ -206,7 +206,7 @@ class TestBuildShutdownCmd: def test_without_restore( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test command without restore flag.""" @@ -223,7 +223,7 @@ class TestBuildShutdownCmd: def test_with_restore( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test command with restore flag.""" @@ -245,7 +245,7 @@ class TestWriteShutdownConfig: def test_returns_false_when_script_missing( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test returns False when adjust script doesn't exist.""" @@ -262,7 +262,7 @@ class TestWriteShutdownConfig: def test_success_calls_run_shutdown_cmd( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test successful config write delegates to _run_shutdown_cmd.""" @@ -287,7 +287,7 @@ class TestRunShutdownCmd: def test_success( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test successful command execution.""" @@ -303,7 +303,7 @@ class TestRunShutdownCmd: def test_returns_false_on_subprocess_error( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test returns False on SubprocessError.""" diff --git a/python_pkg/screen_locker/tests/test_ui_and_timers.py b/python_pkg/screen_locker/tests/test_ui_and_timers.py index c9e3c99..f3ae939 100644 --- a/python_pkg/screen_locker/tests/test_ui_and_timers.py +++ b/python_pkg/screen_locker/tests/test_ui_and_timers.py @@ -24,7 +24,7 @@ class TestUITransitions: def test_clear_container( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test clear_container destroys all child widgets.""" @@ -46,7 +46,7 @@ class TestUITransitions: def test_unlock_screen_saves_and_schedules_close( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test unlock_screen saves log and schedules close.""" @@ -62,7 +62,7 @@ class TestUITransitions: def test_lockout_starts_countdown( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test lockout initializes countdown timer.""" @@ -95,7 +95,7 @@ class TestTimerLogic: def test_update_lockout_countdown_decrements( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test countdown decrements remaining time.""" @@ -111,7 +111,7 @@ class TestTimerLogic: def test_update_lockout_countdown_at_zero( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test countdown at zero returns to workout question.""" @@ -127,7 +127,7 @@ class TestTimerLogic: def test_update_submit_timer_countdown( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test submit timer counts down.""" @@ -145,7 +145,7 @@ class TestTimerLogic: def test_update_submit_timer_enables_when_filled( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test submit enabled when timer done and entries filled.""" @@ -165,7 +165,7 @@ class TestTimerLogic: def test_update_submit_timer_waits_for_entries( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test submit waits when entries not filled.""" @@ -184,7 +184,7 @@ class TestTimerLogic: def test_update_submit_timer_handles_tcl_error( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test timer handles TclError when widgets destroyed.""" @@ -199,7 +199,7 @@ class TestTimerLogic: def test_check_entries_filled_enables_submit( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test check_entries_filled enables submit when all filled.""" @@ -218,7 +218,7 @@ class TestTimerLogic: def test_check_entries_filled_continues_waiting( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test check_entries_filled continues waiting when not filled.""" @@ -236,7 +236,7 @@ class TestTimerLogic: def test_check_entries_filled_handles_tcl_error( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test check_entries_filled handles TclError.""" @@ -256,7 +256,7 @@ class TestAskWorkoutType: def test_ask_workout_type_creates_buttons( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test ask_workout_type creates running and strength buttons.""" @@ -277,7 +277,7 @@ class TestAskRunningDetails: def test_ask_running_details_sets_workout_type( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test ask_running_details sets workout type to running.""" @@ -293,7 +293,7 @@ class TestAskRunningDetails: def test_ask_running_details_creates_entry_fields( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test ask_running_details creates entry fields.""" @@ -312,7 +312,7 @@ class TestAskRunningDetails: def test_ask_running_details_sets_timer( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test ask_running_details initializes submit timer.""" @@ -332,7 +332,7 @@ class TestAskStrengthDetails: def test_ask_strength_details_sets_workout_type( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test ask_strength_details sets workout type to strength.""" @@ -348,7 +348,7 @@ class TestAskStrengthDetails: def test_ask_strength_details_creates_entry_fields( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test ask_strength_details creates entry fields.""" @@ -369,7 +369,7 @@ class TestAskStrengthDetails: def test_ask_strength_details_sets_timer( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test ask_strength_details initializes submit timer.""" @@ -385,7 +385,7 @@ class TestAskStrengthDetails: def test_ask_strength_details_production_timer( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test production mode uses longer submit delay.""" @@ -404,7 +404,7 @@ class TestAskWorkoutDone: def test_ask_workout_done_creates_buttons( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test ask_workout_done creates yes/no buttons.""" @@ -422,7 +422,7 @@ class TestAskIfSick: """Tests for ask_if_sick method.""" def test_ask_if_sick_displays_dialog( - self, mock_tk: MagicMock, _mock_sys_exit: MagicMock, tmp_path: Path + self, mock_tk: MagicMock, mock_sys_exit: MagicMock, tmp_path: Path ) -> None: """Test ask_if_sick shows sick day question.""" locker = create_locker(mock_tk, tmp_path) @@ -436,7 +436,7 @@ class TestSickQuestionButtons: """Tests for _sick_question_buttons method.""" def test_creates_buttons( - self, mock_tk: MagicMock, _mock_sys_exit: MagicMock, tmp_path: Path + self, mock_tk: MagicMock, mock_sys_exit: MagicMock, tmp_path: Path ) -> None: """Test _sick_question_buttons creates yes/no buttons.""" locker = create_locker(mock_tk, tmp_path) @@ -448,7 +448,7 @@ class TestGetSickDayStatus: """Tests for _get_sick_day_status method.""" def test_already_adjusted_today( - self, mock_tk: MagicMock, _mock_sys_exit: MagicMock, tmp_path: Path + self, mock_tk: MagicMock, mock_sys_exit: MagicMock, tmp_path: Path ) -> None: """Test status when sick mode already used today.""" locker = create_locker(mock_tk, tmp_path) @@ -460,7 +460,7 @@ class TestGetSickDayStatus: assert color == "#ffaa00" def test_adjustment_success( - self, mock_tk: MagicMock, _mock_sys_exit: MagicMock, tmp_path: Path + self, mock_tk: MagicMock, mock_sys_exit: MagicMock, tmp_path: Path ) -> None: """Test status when shutdown time adjusted successfully.""" locker = create_locker(mock_tk, tmp_path) @@ -475,7 +475,7 @@ class TestGetSickDayStatus: assert color == "#00aa00" def test_adjustment_failure( - self, mock_tk: MagicMock, _mock_sys_exit: MagicMock, tmp_path: Path + self, mock_tk: MagicMock, mock_sys_exit: MagicMock, tmp_path: Path ) -> None: """Test status when adjustment fails.""" locker = create_locker(mock_tk, tmp_path) diff --git a/python_pkg/screen_locker/tests/test_ui_and_timers_part2.py b/python_pkg/screen_locker/tests/test_ui_and_timers_part2.py index 9764e98..79caf1d 100644 --- a/python_pkg/screen_locker/tests/test_ui_and_timers_part2.py +++ b/python_pkg/screen_locker/tests/test_ui_and_timers_part2.py @@ -18,7 +18,7 @@ class TestHandleSickDay: """Tests for handle_sick_day method.""" def test_sets_up_countdown( - self, mock_tk: MagicMock, _mock_sys_exit: MagicMock, tmp_path: Path + self, mock_tk: MagicMock, mock_sys_exit: MagicMock, tmp_path: Path ) -> None: """Test handle_sick_day initializes sick day flow.""" locker = create_locker(mock_tk, tmp_path) @@ -38,7 +38,7 @@ class TestShowSickDayUi: """Tests for _show_sick_day_ui method.""" def test_displays_ui( - self, mock_tk: MagicMock, _mock_sys_exit: MagicMock, tmp_path: Path + self, mock_tk: MagicMock, mock_sys_exit: MagicMock, tmp_path: Path ) -> None: """Test _show_sick_day_ui displays labels.""" locker = create_locker(mock_tk, tmp_path) diff --git a/python_pkg/screen_locker/tests/test_ui_flows_part2.py b/python_pkg/screen_locker/tests/test_ui_flows_part2.py index e7a4f06..0e8facf 100644 --- a/python_pkg/screen_locker/tests/test_ui_flows_part2.py +++ b/python_pkg/screen_locker/tests/test_ui_flows_part2.py @@ -17,7 +17,7 @@ class TestUpdateSickCountdownAtZero: def test_records_sick_day_and_unlocks_at_zero( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test countdown at zero records sick day and calls unlock.""" diff --git a/python_pkg/screen_locker/tests/test_verify_data.py b/python_pkg/screen_locker/tests/test_verify_data.py index e1d6c2d..368d657 100644 --- a/python_pkg/screen_locker/tests/test_verify_data.py +++ b/python_pkg/screen_locker/tests/test_verify_data.py @@ -23,7 +23,7 @@ class TestVerifyRunningData: def test_valid_running_data( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test valid running data triggers unlock attempt.""" @@ -40,7 +40,7 @@ class TestVerifyRunningData: def test_invalid_distance_zero( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test zero distance is rejected.""" @@ -56,7 +56,7 @@ class TestVerifyRunningData: def test_invalid_distance_too_high( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test distance over max is rejected.""" @@ -72,7 +72,7 @@ class TestVerifyRunningData: def test_invalid_time_zero( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test zero time is rejected.""" @@ -88,7 +88,7 @@ class TestVerifyRunningData: def test_invalid_time_too_high( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test time over max is rejected.""" @@ -104,7 +104,7 @@ class TestVerifyRunningData: def test_invalid_pace_zero( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test zero pace is rejected.""" @@ -120,7 +120,7 @@ class TestVerifyRunningData: def test_invalid_pace_too_high( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test pace over max is rejected.""" @@ -136,7 +136,7 @@ class TestVerifyRunningData: def test_pace_mismatch( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test pace mismatch is rejected.""" @@ -153,7 +153,7 @@ class TestVerifyRunningData: def test_invalid_number_format( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test non-numeric input is rejected.""" @@ -173,7 +173,7 @@ class TestVerifyStrengthData: def test_valid_strength_data( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test valid strength data triggers unlock attempt.""" @@ -190,7 +190,7 @@ class TestVerifyStrengthData: def test_valid_multiple_exercises( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test valid data with multiple exercises.""" @@ -210,7 +210,7 @@ class TestVerifyStrengthData: def test_mismatched_list_lengths( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test mismatched list lengths are rejected.""" @@ -229,7 +229,7 @@ class TestVerifyStrengthData: def test_short_exercise_name( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test short exercise names are rejected.""" @@ -245,7 +245,7 @@ class TestVerifyStrengthData: def test_invalid_sets_zero( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test zero sets is rejected.""" @@ -261,7 +261,7 @@ class TestVerifyStrengthData: def test_invalid_sets_too_high( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test sets over max is rejected.""" @@ -277,7 +277,7 @@ class TestVerifyStrengthData: def test_invalid_reps_zero( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test zero reps is rejected.""" @@ -293,7 +293,7 @@ class TestVerifyStrengthData: def test_invalid_reps_too_high( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test reps over max is rejected.""" @@ -309,7 +309,7 @@ class TestVerifyStrengthData: def test_invalid_weight_negative( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test negative weight is rejected.""" @@ -325,7 +325,7 @@ class TestVerifyStrengthData: def test_invalid_weight_too_high( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test weight over max is rejected.""" @@ -341,7 +341,7 @@ class TestVerifyStrengthData: def test_total_weight_mismatch( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test total weight mismatch is rejected.""" @@ -357,7 +357,7 @@ class TestVerifyStrengthData: def test_invalid_format( self, mock_tk: MagicMock, - _mock_sys_exit: MagicMock, + mock_sys_exit: MagicMock, tmp_path: Path, ) -> None: """Test invalid format is rejected.""" @@ -375,7 +375,7 @@ class TestVariableReps: """Tests for variable reps format in strength verification.""" def test_valid_variable_reps( - self, mock_tk: MagicMock, _mock_sys_exit: MagicMock, tmp_path: Path + self, mock_tk: MagicMock, mock_sys_exit: MagicMock, tmp_path: Path ) -> None: """Test valid variable reps with + separator.""" locker = create_locker(mock_tk, tmp_path) @@ -391,7 +391,7 @@ class TestVariableReps: locker._attempt_unlock.assert_called_once() def test_variable_reps_count_mismatch( - self, mock_tk: MagicMock, _mock_sys_exit: MagicMock, tmp_path: Path + self, mock_tk: MagicMock, mock_sys_exit: MagicMock, tmp_path: Path ) -> None: """Test variable reps count not matching sets.""" locker = create_locker(mock_tk, tmp_path) diff --git a/python_pkg/steam_backlog_enforcer/library_hider.py b/python_pkg/steam_backlog_enforcer/library_hider.py index 21e4d61..6c5d23f 100644 --- a/python_pkg/steam_backlog_enforcer/library_hider.py +++ b/python_pkg/steam_backlog_enforcer/library_hider.py @@ -24,8 +24,8 @@ import pwd import shutil import subprocess import time -import urllib.request +import requests import websockets logger = logging.getLogger(__name__) @@ -42,10 +42,9 @@ _STEAM_STARTUP_WAIT = 45 def _get_shared_js_ws_url() -> str | None: """Query the CDP HTTP endpoint and return the SharedJSContext WS URL.""" - url = f"http://127.0.0.1:{_CDP_PORT}/json" try: - with urllib.request.urlopen(url, timeout=5) as resp: - targets = json.loads(resp.read()) + resp = requests.get(f"http://127.0.0.1:{_CDP_PORT}/json", timeout=5) + targets = resp.json() except (OSError, ValueError): return None diff --git a/python_pkg/steam_backlog_enforcer/tests/test_game_install_part2.py b/python_pkg/steam_backlog_enforcer/tests/test_game_install_part2.py index 099b4a2..7aa18b1 100644 --- a/python_pkg/steam_backlog_enforcer/tests/test_game_install_part2.py +++ b/python_pkg/steam_backlog_enforcer/tests/test_game_install_part2.py @@ -63,7 +63,7 @@ class TestRemoveGameDirs: (tmp_path / "shadercache" / "440").mkdir(parents=True) call_count = 0 - def fake_rmtree(path: object, **_kw: object) -> None: + def fake_rmtree(_path: object, **_kw: object) -> None: nonlocal call_count call_count += 1 msg = "perm" diff --git a/python_pkg/steam_backlog_enforcer/tests/test_hltb.py b/python_pkg/steam_backlog_enforcer/tests/test_hltb.py index 9e07c6c..083128d 100644 --- a/python_pkg/steam_backlog_enforcer/tests/test_hltb.py +++ b/python_pkg/steam_backlog_enforcer/tests/test_hltb.py @@ -25,6 +25,7 @@ from python_pkg.steam_backlog_enforcer.hltb import ( ) if TYPE_CHECKING: + from collections.abc import Callable from pathlib import Path @@ -243,7 +244,7 @@ def _make_ctx( session: MagicMock, *, cache: dict[int, float] | None = None, - progress_cb: Any = None, + progress_cb: Callable[..., object] | None = None, ) -> _SearchCtx: return _SearchCtx( session=session, diff --git a/python_pkg/steam_backlog_enforcer/tests/test_hltb_part2.py b/python_pkg/steam_backlog_enforcer/tests/test_hltb_part2.py index 2253828..4f0eedc 100644 --- a/python_pkg/steam_backlog_enforcer/tests/test_hltb_part2.py +++ b/python_pkg/steam_backlog_enforcer/tests/test_hltb_part2.py @@ -33,7 +33,7 @@ class TestFetchHltbTimesCached: ): # fetch_hltb_times modifies cache in-place def add_to_cache( - games: object, + _games: object, cache: dict[int, float] | None = None, progress_cb: object = None, ) -> list[object]: @@ -85,7 +85,7 @@ class TestFetchHltbTimesCached: ): def add_found( - games: object, + _games: object, cache: dict[int, float] | None = None, progress_cb: object = None, ) -> list[object]: diff --git a/python_pkg/steam_backlog_enforcer/tests/test_library_hider.py b/python_pkg/steam_backlog_enforcer/tests/test_library_hider.py index beb9913..4ebad97 100644 --- a/python_pkg/steam_backlog_enforcer/tests/test_library_hider.py +++ b/python_pkg/steam_backlog_enforcer/tests/test_library_hider.py @@ -37,11 +37,9 @@ class TestGetSharedJsWsUrl: {"title": "Other", "webSocketDebuggerUrl": "ws://other"}, ] mock_resp = MagicMock() - mock_resp.read.return_value = json.dumps(targets).encode() - mock_resp.__enter__ = MagicMock(return_value=mock_resp) - mock_resp.__exit__ = MagicMock(return_value=False) + mock_resp.json.return_value = targets with patch( - "python_pkg.steam_backlog_enforcer.library_hider.urllib.request.urlopen", + "python_pkg.steam_backlog_enforcer.library_hider.requests.get", return_value=mock_resp, ): result = _get_shared_js_ws_url() @@ -50,18 +48,16 @@ class TestGetSharedJsWsUrl: def test_no_shared_context(self) -> None: targets = [{"title": "Other", "webSocketDebuggerUrl": "ws://other"}] mock_resp = MagicMock() - mock_resp.read.return_value = json.dumps(targets).encode() - mock_resp.__enter__ = MagicMock(return_value=mock_resp) - mock_resp.__exit__ = MagicMock(return_value=False) + mock_resp.json.return_value = targets with patch( - "python_pkg.steam_backlog_enforcer.library_hider.urllib.request.urlopen", + "python_pkg.steam_backlog_enforcer.library_hider.requests.get", return_value=mock_resp, ): assert _get_shared_js_ws_url() is None def test_connection_error(self) -> None: with patch( - "python_pkg.steam_backlog_enforcer.library_hider.urllib.request.urlopen", + "python_pkg.steam_backlog_enforcer.library_hider.requests.get", side_effect=OSError, ): assert _get_shared_js_ws_url() is None diff --git a/python_pkg/steam_backlog_enforcer/tests/test_library_hider_part2.py b/python_pkg/steam_backlog_enforcer/tests/test_library_hider_part2.py index 5c745da..7078779 100644 --- a/python_pkg/steam_backlog_enforcer/tests/test_library_hider_part2.py +++ b/python_pkg/steam_backlog_enforcer/tests/test_library_hider_part2.py @@ -3,6 +3,7 @@ from __future__ import annotations import os +import tempfile from unittest.mock import MagicMock, patch from python_pkg.steam_backlog_enforcer.library_hider import ( @@ -32,7 +33,10 @@ class TestRunAsUser: with ( patch(f"{PKG}.os.geteuid", return_value=0), patch(f"{PKG}.pwd.getpwnam", return_value=mock_pw), - patch.dict(os.environ, {"DISPLAY": ":1", "XAUTHORITY": "/tmp/.X"}), + patch.dict( + os.environ, + {"DISPLAY": ":1", "XAUTHORITY": tempfile.gettempdir() + "/.X"}, + ), patch(f"{PKG}.subprocess.Popen") as mock_popen, ): _run_as_user(["steam", "-shutdown"], "alice") diff --git a/python_pkg/steam_backlog_enforcer/tests/test_main_part2.py b/python_pkg/steam_backlog_enforcer/tests/test_main_part2.py index 10939f3..f7d606c 100644 --- a/python_pkg/steam_backlog_enforcer/tests/test_main_part2.py +++ b/python_pkg/steam_backlog_enforcer/tests/test_main_part2.py @@ -55,9 +55,9 @@ class TestFinalizeCompletion: ): def set_next( - games: object, + _games: object, s: State, - c: object, + _c: object, ) -> None: s.current_app_id = 2 s.current_game_name = "NewGame" @@ -89,9 +89,9 @@ class TestFinalizeCompletion: ): def set_none( - games: object, + _games: object, s: State, - c: object, + _c: object, ) -> None: s.current_app_id = None @@ -112,9 +112,9 @@ class TestFinalizeCompletion: ): def set_2( - games: object, + _games: object, s: State, - c: object, + _c: object, ) -> None: s.current_app_id = 2 s.current_game_name = "Next" @@ -137,9 +137,9 @@ class TestFinalizeCompletion: ): def set_2( - games: object, + _games: object, s: State, - c: object, + _c: object, ) -> None: s.current_app_id = 2 s.current_game_name = "Next" diff --git a/python_pkg/steam_backlog_enforcer/tests/test_scanning.py b/python_pkg/steam_backlog_enforcer/tests/test_scanning.py index 2efbe96..c9e6a55 100644 --- a/python_pkg/steam_backlog_enforcer/tests/test_scanning.py +++ b/python_pkg/steam_backlog_enforcer/tests/test_scanning.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Any +from typing import TYPE_CHECKING from unittest.mock import MagicMock, patch from python_pkg.steam_backlog_enforcer.config import Config, State @@ -15,6 +15,9 @@ from python_pkg.steam_backlog_enforcer.scanning import ( ) from python_pkg.steam_backlog_enforcer.steam_api import GameInfo +if TYPE_CHECKING: + from collections.abc import Callable + def _game( app_id: int = 1, @@ -41,8 +44,8 @@ class TestDoScan: mock_client = MagicMock() def build_game_list( - skip_app_ids: Any = None, - progress_callback: Any = None, + skip_app_ids: object = None, + progress_callback: Callable[..., object] | None = None, ) -> list[GameInfo]: # Trigger progress callback to cover those lines. if progress_callback: @@ -58,7 +61,7 @@ class TestDoScan: ), patch( "python_pkg.steam_backlog_enforcer.scanning.fetch_hltb_times_cached", - side_effect=lambda games, progress_cb=None: ( + side_effect=lambda _games, progress_cb=None: ( progress_cb(1, 1, 1, "TF2") if progress_cb else None, {440: 20.0}, )[1], @@ -84,8 +87,8 @@ class TestDoScan: mock_client = MagicMock() def build_game_list( - skip_app_ids: Any = None, - progress_callback: Any = None, + skip_app_ids: object = None, + progress_callback: Callable[..., object] | None = None, ) -> list[GameInfo]: if progress_callback: # current=1, total=2 → not %50 and not ==total → covers False branch diff --git a/python_pkg/steam_backlog_enforcer/tests/test_steam_api.py b/python_pkg/steam_backlog_enforcer/tests/test_steam_api.py index 527c419..6571dc9 100644 --- a/python_pkg/steam_backlog_enforcer/tests/test_steam_api.py +++ b/python_pkg/steam_backlog_enforcer/tests/test_steam_api.py @@ -253,7 +253,7 @@ class TestSteamAPIClient: def test_fetch_one_game(self) -> None: client = SteamAPIClient("key", "id") - ach = AchievementInfo("A1", "Ach1", True, 100) + ach = AchievementInfo("A1", "Ach1", achieved=True, unlock_time=100) with patch.object(client, "get_achievement_details", return_value=[ach]): result = client._fetch_one_game( {"appid": 440, "name": "TF2", "playtime_forever": 60}, @@ -275,7 +275,7 @@ class TestSteamAPIClient: def test_build_game_list(self) -> None: client = SteamAPIClient("key", "id") - ach = AchievementInfo("A1", "Ach1", True, 100) + ach = AchievementInfo("A1", "Ach1", achieved=True, unlock_time=100) with ( patch.object( client, @@ -322,7 +322,7 @@ class TestSteamAPIClient: def test_refresh_single_game(self) -> None: client = SteamAPIClient("key", "id") - ach = AchievementInfo("A1", "Ach1", True, 100) + ach = AchievementInfo("A1", "Ach1", achieved=True, unlock_time=100) with patch.object(client, "get_achievement_details", return_value=[ach]): result = client.refresh_single_game(440, "TF2", 60) assert result is not None diff --git a/python_pkg/steam_backlog_enforcer/tests/test_store_blocker.py b/python_pkg/steam_backlog_enforcer/tests/test_store_blocker.py index 7a97c69..f87b3a4 100644 --- a/python_pkg/steam_backlog_enforcer/tests/test_store_blocker.py +++ b/python_pkg/steam_backlog_enforcer/tests/test_store_blocker.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from unittest.mock import MagicMock, patch from python_pkg.steam_backlog_enforcer.store_blocker import ( @@ -382,7 +382,7 @@ class TestBlockStoreIptables: ] call_count = 0 - def side_effect(*args: Any, **kwargs: Any) -> MagicMock: + def side_effect(*_args: object, **_kwargs: object) -> MagicMock: nonlocal call_count idx = min(call_count, len(results) - 1) call_count += 1 diff --git a/python_pkg/steam_backlog_enforcer/tests/test_store_blocker_part2.py b/python_pkg/steam_backlog_enforcer/tests/test_store_blocker_part2.py index aeddd43..67a5021 100644 --- a/python_pkg/steam_backlog_enforcer/tests/test_store_blocker_part2.py +++ b/python_pkg/steam_backlog_enforcer/tests/test_store_blocker_part2.py @@ -38,7 +38,7 @@ class TestDisableHostsProtection: def run_side_effect( cmd: list[str], - **kwargs: object, + **_kwargs: object, ) -> MagicMock: if any("findmnt" in str(c) for c in cmd): return findmnt_found @@ -52,7 +52,7 @@ class TestDisableHostsProtection: def run_side_effect( cmd: list[str], - **kwargs: object, + **_kwargs: object, ) -> MagicMock: if any("findmnt" in str(c) for c in cmd): return findmnt_missing diff --git a/python_pkg/word_frequency/_translator_cli.py b/python_pkg/word_frequency/_translator_cli.py index 9ffd342..7f78ac9 100644 --- a/python_pkg/word_frequency/_translator_cli.py +++ b/python_pkg/word_frequency/_translator_cli.py @@ -196,7 +196,7 @@ def main(argv: Sequence[str] | None = None) -> int: parser = _build_parser() args = parser.parse_args(argv) - if not _trans._check_argos(): + if not _trans.check_argos(): sys.stderr.write( "Error: argostranslate is not installed.\n" "Install it with: pip install argostranslate\n", diff --git a/python_pkg/word_frequency/tests/_translator_helpers.py b/python_pkg/word_frequency/tests/_translator_helpers.py index dbc69f3..9e92762 100644 --- a/python_pkg/word_frequency/tests/_translator_helpers.py +++ b/python_pkg/word_frequency/tests/_translator_helpers.py @@ -67,7 +67,7 @@ class ArgosAvailableMock: translator, "_ensure_language_pair", lambda _f, _t: None ) self._check_argos_patcher = patch.object( - translator, "_check_argos", return_value=True + translator, "check_argos", return_value=True ) self._sys_modules_patcher.start() diff --git a/python_pkg/word_frequency/tests/conftest.py b/python_pkg/word_frequency/tests/conftest.py index 3aefac0..82d9bb8 100644 --- a/python_pkg/word_frequency/tests/conftest.py +++ b/python_pkg/word_frequency/tests/conftest.py @@ -15,9 +15,9 @@ from python_pkg.word_frequency import translator @pytest.fixture -def _mock_argos_unavailable() -> Generator[None, None, None]: +def mock_argos_unavailable() -> Generator[None, None, None]: """Mock argostranslate being unavailable (for legacy tests).""" - with patch.object(translator, "_check_argos", return_value=False): + with patch.object(translator, "check_argos", return_value=False): yield diff --git a/python_pkg/word_frequency/tests/test_cache.py b/python_pkg/word_frequency/tests/test_cache.py index b130890..9ed6e43 100644 --- a/python_pkg/word_frequency/tests/test_cache.py +++ b/python_pkg/word_frequency/tests/test_cache.py @@ -28,9 +28,11 @@ class TestGetCacheDir: """Tests for get_cache_dir.""" def test_returns_default(self, tmp_path: Path) -> None: - with patch("python_pkg.word_frequency.cache.DEFAULT_CACHE_DIR", tmp_path): - with patch.dict("os.environ", {}, clear=False): - d = get_cache_dir() + with ( + patch("python_pkg.word_frequency.cache.DEFAULT_CACHE_DIR", tmp_path), + patch.dict("os.environ", {}, clear=False), + ): + d = get_cache_dir() assert d == tmp_path def test_respects_env_var(self, tmp_path: Path) -> None: diff --git a/python_pkg/word_frequency/tests/test_cache_decks.py b/python_pkg/word_frequency/tests/test_cache_decks.py index acb8c5c..0d6c7a8 100644 --- a/python_pkg/word_frequency/tests/test_cache_decks.py +++ b/python_pkg/word_frequency/tests/test_cache_decks.py @@ -142,14 +142,14 @@ class TestAnkiDeckCache: cache = AnkiDeckCache(cache_dir=tmp_path) fp = tmp_path / "text.txt" fp.write_text("hello", encoding="utf-8") - dk = AnkiDeckKey(fp, 10, "es", False, True) + dk = AnkiDeckKey(fp, 10, "es", include_context=False, all_vocab=True) assert cache.get(dk) is None def test_get_hash_mismatch(self, tmp_path: Path) -> None: cache = AnkiDeckCache(cache_dir=tmp_path) fp = tmp_path / "text.txt" fp.write_text("hello", encoding="utf-8") - dk = AnkiDeckKey(fp, 10, "es", False, True) + dk = AnkiDeckKey(fp, 10, "es", include_context=False, all_vocab=True) cache.set(dk, "content", "hello", 1, 1) # Modify file to change hash fp.write_text("changed content", encoding="utf-8") @@ -160,7 +160,7 @@ class TestAnkiDeckCache: cache = AnkiDeckCache(cache_dir=tmp_path) fp = tmp_path / "text.txt" fp.write_text("hello", encoding="utf-8") - dk = AnkiDeckKey(fp, 10, "es", False, True) + dk = AnkiDeckKey(fp, 10, "es", include_context=False, all_vocab=True) cache.set(dk, "content", "hello", 1, 1) # Tamper with stored hash in metadata m = cache._load_metadata() @@ -174,7 +174,7 @@ class TestAnkiDeckCache: cache = AnkiDeckCache(cache_dir=tmp_path) fp = tmp_path / "text.txt" fp.write_text("hello", encoding="utf-8") - dk = AnkiDeckKey(fp, 10, "es", False, True) + dk = AnkiDeckKey(fp, 10, "es", include_context=False, all_vocab=True) cache.set(dk, "content", "hello", 1, 1) # Remove all .txt files in cache dir for f in cache.cache_dir.glob("*.txt"): @@ -185,7 +185,7 @@ class TestAnkiDeckCache: cache = AnkiDeckCache(cache_dir=tmp_path) fp = tmp_path / "text.txt" fp.write_text("hello", encoding="utf-8") - dk = AnkiDeckKey(fp, 10, "es", False, True) + dk = AnkiDeckKey(fp, 10, "es", include_context=False, all_vocab=True) cache.set(dk, "content", "hello", 1, 1) # Mock read_text to raise OSError with patch("pathlib.Path.read_text", side_effect=OSError("read error")): @@ -212,7 +212,7 @@ class TestAnkiDeckCache: cache = AnkiDeckCache(cache_dir=tmp_path) fp = tmp_path / "text.txt" fp.write_text("hello", encoding="utf-8") - dk = AnkiDeckKey(fp, 10, "es", False, True) + dk = AnkiDeckKey(fp, 10, "es", include_context=False, all_vocab=True) cache.set(dk, "content", "hello", 1, 1) cache.clear() assert cache.get(dk) is None @@ -226,7 +226,7 @@ class TestAnkiDeckCache: cache = AnkiDeckCache(cache_dir=tmp_path) fp = tmp_path / "text.txt" fp.write_text("hello", encoding="utf-8") - dk = AnkiDeckKey(fp, 10, "es", False, True) + dk = AnkiDeckKey(fp, 10, "es", include_context=False, all_vocab=True) cache.set(dk, "content", "hello", 1, 1) stats = cache.stats() assert stats["total_entries"] == 1 diff --git a/python_pkg/word_frequency/tests/test_generation.py b/python_pkg/word_frequency/tests/test_generation.py index 60abf5e..681548b 100644 --- a/python_pkg/word_frequency/tests/test_generation.py +++ b/python_pkg/word_frequency/tests/test_generation.py @@ -118,19 +118,19 @@ class TestCaching: mock.return_value.set.assert_called_once() def test_get_cached_deck_force(self) -> None: - key = AnkiDeckKey(Path("x"), 10, "es", False, True) + key = AnkiDeckKey(Path("x"), 10, "es", include_context=False, all_vocab=True) result = get_cached_deck(key, force=True) assert result is None def test_get_cached_deck_delegates(self) -> None: - key = AnkiDeckKey(Path("x"), 10, "es", False, True) + key = AnkiDeckKey(Path("x"), 10, "es", include_context=False, all_vocab=True) with patch("python_pkg.word_frequency._generation.get_anki_deck_cache") as mock: mock.return_value.get.return_value = ("c", "e", 2, 5) result = get_cached_deck(key) assert result == ("c", "e", 2, 5) def test_cache_deck_delegates(self) -> None: - key = AnkiDeckKey(Path("x"), 10, "es", False, True) + key = AnkiDeckKey(Path("x"), 10, "es", include_context=False, all_vocab=True) with patch("python_pkg.word_frequency._generation.get_anki_deck_cache") as mock: cache_deck(key, "content", "excerpt", 2, 5) mock.return_value.set.assert_called_once() @@ -215,7 +215,7 @@ VOCAB_DUMP_END "python_pkg.word_frequency._generation.get_anki_deck_cache" ) as mock_cache, ): - content, excerpt, num_words, max_rank = generate_flashcards( + content, excerpt, _, _ = generate_flashcards( fp, 5, FlashcardOptions(source_lang="en"), @@ -331,7 +331,7 @@ VOCAB_DUMP_END ), patch("python_pkg.word_frequency._generation.get_anki_deck_cache"), ): - content, excerpt, num_words, max_rank = generate_flashcards( + content, _, _, _ = generate_flashcards( fp, 5, FlashcardOptions(source_lang=None, no_translate=True) ) assert content == "deck" diff --git a/python_pkg/word_frequency/tests/test_learning_batch_part2.py b/python_pkg/word_frequency/tests/test_learning_batch_part2.py index 53ee986..2164486 100644 --- a/python_pkg/word_frequency/tests/test_learning_batch_part2.py +++ b/python_pkg/word_frequency/tests/test_learning_batch_part2.py @@ -3,7 +3,6 @@ from __future__ import annotations from collections import Counter -from typing import Any from unittest.mock import patch from python_pkg.word_frequency._learning_batch import ( @@ -106,8 +105,8 @@ class TestGenerateBatchSectionWithTranslation: def fake_batch( words: list[str], - from_lang: Any, - to_lang: Any, + _from_lang: str | None, + _to_lang: str | None, ) -> list[TranslationResult]: return [ TranslationResult( diff --git a/python_pkg/word_frequency/tests/test_learning_pipe.py b/python_pkg/word_frequency/tests/test_learning_pipe.py index 0dfdc7e..472fcd6 100644 --- a/python_pkg/word_frequency/tests/test_learning_pipe.py +++ b/python_pkg/word_frequency/tests/test_learning_pipe.py @@ -29,7 +29,7 @@ if TYPE_CHECKING: @pytest.fixture -def _mock_translation() -> Generator[MagicMock, None, None]: +def mock_translation() -> Generator[MagicMock, None, None]: """Mock translation to avoid requiring argostranslate.""" def fake_batch_translate( @@ -262,7 +262,7 @@ class TestMain: """Tests for main CLI function.""" def test_basic_text_input( - self, caplog: pytest.LogCaptureFixture, _mock_translation: None + self, caplog: pytest.LogCaptureFixture, mock_translation: None ) -> None: """Test with text input.""" with caplog.at_level(logging.INFO): @@ -280,7 +280,7 @@ class TestMain: assert "LANGUAGE LEARNING LESSON" in caplog.text def test_file_input( - self, tmp_path: Path, caplog: pytest.LogCaptureFixture, _mock_translation: None + self, tmp_path: Path, caplog: pytest.LogCaptureFixture, mock_translation: None ) -> None: """Test with file input.""" test_file = tmp_path / "test.txt" @@ -300,7 +300,7 @@ class TestMain: assert exit_code == 0 assert "hello" in caplog.text.lower() - def test_output_to_file(self, tmp_path: Path, _mock_translation: None) -> None: + def test_output_to_file(self, tmp_path: Path, mock_translation: None) -> None: """Test outputting to file.""" output_file = tmp_path / "lesson.txt" @@ -319,7 +319,7 @@ class TestMain: content = output_file.read_text(encoding="utf-8") assert "LANGUAGE LEARNING LESSON" in content - def test_custom_stopwords(self, tmp_path: Path, _mock_translation: None) -> None: + def test_custom_stopwords(self, tmp_path: Path, mock_translation: None) -> None: """Test with custom stopwords file.""" stopwords_file = tmp_path / "stop.txt" stopwords_file.write_text("hello\n", encoding="utf-8") @@ -340,7 +340,7 @@ class TestMain: # "hello" should be filtered by custom stopwords def test_multiple_batches_option( - self, caplog: pytest.LogCaptureFixture, _mock_translation: None + self, caplog: pytest.LogCaptureFixture, mock_translation: None ) -> None: """Test --batches option.""" text = " ".join(f"word{i}" * (50 - i) for i in range(30)) @@ -385,7 +385,7 @@ class TestMain: assert exit_code == 1 def test_output_to_file_branch( - self, tmp_path: Path, _mock_translation: None + self, tmp_path: Path, mock_translation: None ) -> None: """Test --output to verify the file writing path.""" out = tmp_path / "out.txt" diff --git a/python_pkg/word_frequency/tests/test_learning_pipe_part2.py b/python_pkg/word_frequency/tests/test_learning_pipe_part2.py index e3448cf..4ee9c81 100644 --- a/python_pkg/word_frequency/tests/test_learning_pipe_part2.py +++ b/python_pkg/word_frequency/tests/test_learning_pipe_part2.py @@ -2,7 +2,6 @@ from __future__ import annotations -from typing import Any from unittest.mock import patch from python_pkg.word_frequency._learning_constants import LessonConfig @@ -19,8 +18,8 @@ class TestDoTranslateBranch: def fake_batch( words: list[str], - from_lang: Any, - to_lang: Any, + _from_lang: str | None, + _to_lang: str | None, ) -> list[TranslationResult]: return [ TranslationResult( @@ -56,8 +55,8 @@ class TestDoTranslateBranch: def fake_batch( words: list[str], - from_lang: Any, - to_lang: Any, + from_lang: str | None, + to_lang: str | None, ) -> list[TranslationResult]: return [ TranslationResult( diff --git a/python_pkg/word_frequency/tests/test_parsing.py b/python_pkg/word_frequency/tests/test_parsing.py index 0a84a35..3d5c158 100644 --- a/python_pkg/word_frequency/tests/test_parsing.py +++ b/python_pkg/word_frequency/tests/test_parsing.py @@ -109,7 +109,7 @@ VOCAB_DUMP_END Excerpt: "hello world foo" """ - excerpt, length, max_rank, vocab = parse_inverse_mode_output(output) + _, length, max_rank, _ = parse_inverse_mode_output(output) assert length == 3 assert max_rank == 0 @@ -122,17 +122,17 @@ Excerpt: def test_short_longest_excerpt_line(self) -> None: output = "LONGEST EXCERPT: 0" - excerpt, length, max_rank, vocab = parse_inverse_mode_output(output) + _, length, _, _ = parse_inverse_mode_output(output) assert length == 0 def test_too_few_parts_in_longest_excerpt(self) -> None: output = "LONGEST EXCERPT:" - excerpt, length, max_rank, vocab = parse_inverse_mode_output(output) + _, length, _, _ = parse_inverse_mode_output(output) assert length == 0 def test_rarest_word_without_hash_number(self) -> None: output = "Rarest word used: unknown" - excerpt, length, max_rank, vocab = parse_inverse_mode_output(output) + _, _, max_rank, _ = parse_inverse_mode_output(output) assert max_rank == 0 @@ -165,7 +165,7 @@ class TestParseTargetLengthBlock: "[Length 3] Vocab needed: 2", " Words: hello(#1)", ] - excerpt, words = _parse_target_length_block(lines, 3) + excerpt, _ = _parse_target_length_block(lines, 3) assert excerpt == "" def test_no_words_line(self) -> None: diff --git a/python_pkg/word_frequency/tests/test_translator.py b/python_pkg/word_frequency/tests/test_translator.py index 0930843..dd84876 100644 --- a/python_pkg/word_frequency/tests/test_translator.py +++ b/python_pkg/word_frequency/tests/test_translator.py @@ -195,7 +195,7 @@ class TestTranslateWordsBatch: mock_parent.package = mock_package_module with ( - patch.object(translator, "_check_argos", return_value=True), + patch.object(translator, "check_argos", return_value=True), patch.object(translator, "argostranslate", mock_parent, create=True), patch.dict( "sys.modules", diff --git a/python_pkg/word_frequency/tests/test_translator_cli.py b/python_pkg/word_frequency/tests/test_translator_cli.py index d496183..481fce9 100644 --- a/python_pkg/word_frequency/tests/test_translator_cli.py +++ b/python_pkg/word_frequency/tests/test_translator_cli.py @@ -158,7 +158,7 @@ class TestHandleTranslation: _cli._trans, "translate_words_batch", return_value=[ - TranslationResult("hello", "hola", "en", "es", True), + TranslationResult("hello", "hola", "en", "es", success=True), ], ), patch.object( @@ -195,7 +195,7 @@ class TestHandleTranslation: _cli._trans, "translate_words_batch", return_value=[ - TranslationResult("hello", "hola", "en", "es", True), + TranslationResult("hello", "hola", "en", "es", success=True), ], ), patch.object( @@ -219,8 +219,15 @@ class TestHandleTranslation: _cli._trans, "translate_words_batch", return_value=[ - TranslationResult("hello", "hola", "en", "es", True), - TranslationResult("xyz", "", "en", "es", False, "error"), + TranslationResult("hello", "hola", "en", "es", success=True), + TranslationResult( + "xyz", + "", + "en", + "es", + success=False, + error="error", + ), ], ), patch.object( @@ -237,7 +244,7 @@ class TestMain: """Tests for main entry point.""" def test_argos_not_available(self, capsys: pytest.CaptureFixture[str]) -> None: - with patch.object(_cli._trans, "_check_argos", return_value=False): + with patch.object(_cli._trans, "check_argos", return_value=False): result = main(["--text", "hello", "--from", "en", "--to", "es"]) assert result == 1 captured = capsys.readouterr() @@ -245,7 +252,7 @@ class TestMain: def test_list_languages(self) -> None: with ( - patch.object(_cli._trans, "_check_argos", return_value=True), + patch.object(_cli._trans, "check_argos", return_value=True), patch.object( _cli._trans, "get_installed_languages", @@ -257,7 +264,7 @@ class TestMain: def test_list_available(self) -> None: with ( - patch.object(_cli._trans, "_check_argos", return_value=True), + patch.object(_cli._trans, "check_argos", return_value=True), patch.object(_cli._trans, "get_available_packages", return_value=[]), ): result = main(["--list-available"]) @@ -265,7 +272,7 @@ class TestMain: def test_download(self, capsys: pytest.CaptureFixture[str]) -> None: with ( - patch.object(_cli._trans, "_check_argos", return_value=True), + patch.object(_cli._trans, "check_argos", return_value=True), patch.object( _cli._trans, "download_languages", @@ -276,7 +283,7 @@ class TestMain: assert result == 0 def test_no_input_shows_help(self) -> None: - with patch.object(_cli._trans, "_check_argos", return_value=True): + with patch.object(_cli._trans, "check_argos", return_value=True): result = main([]) assert result == 1 @@ -284,7 +291,7 @@ class TestMain: self, capsys: pytest.CaptureFixture[str] ) -> None: with ( - patch.object(_cli._trans, "_check_argos", return_value=True), + patch.object(_cli._trans, "check_argos", return_value=True), patch.object( _cli._trans, "read_file", diff --git a/python_pkg/word_frequency/tests/test_translator_helpers_full.py b/python_pkg/word_frequency/tests/test_translator_helpers_full.py index 3f74ea3..3270a67 100644 --- a/python_pkg/word_frequency/tests/test_translator_helpers_full.py +++ b/python_pkg/word_frequency/tests/test_translator_helpers_full.py @@ -3,6 +3,7 @@ from __future__ import annotations import importlib +import tempfile from typing import TYPE_CHECKING from unittest.mock import MagicMock, patch @@ -235,7 +236,7 @@ class TestEnsureLanguagePair: mock_pkg = MagicMock() mock_pkg.from_code = "en" mock_pkg.to_code = "es" - mock_pkg.download.return_value = "/tmp/pkg.argosmodel" + mock_pkg.download.return_value = tempfile.gettempdir() + "/pkg.argosmodel" mock_argos = MagicMock() mock_argos.translate.get_installed_languages.return_value = [ mock_from, @@ -262,7 +263,7 @@ class TestEnsureLanguagePair: mock_pkg = MagicMock() mock_pkg.from_code = "en" mock_pkg.to_code = "es" - mock_pkg.download.return_value = "/tmp/pkg" + mock_pkg.download.return_value = tempfile.gettempdir() + "/pkg" mock_argos = MagicMock() mock_argos.translate.get_installed_languages.return_value = [mock_to] mock_argos.package.get_available_packages.return_value = [mock_pkg] @@ -275,14 +276,14 @@ class TestFormatTranslations: def test_failed_with_no_error(self) -> None: results = [ - TranslationResult("xyz", "", "en", "es", False), + TranslationResult("xyz", "", "en", "es", success=False), ] output = format_translations(results) assert "[Failed]" in output def test_all_failed_max_trans(self) -> None: results = [ - TranslationResult("xyz", "", "en", "es", False, "err"), + TranslationResult("xyz", "", "en", "es", success=False, error="err"), ] output = format_translations(results) assert "Translation" in output diff --git a/python_pkg/word_frequency/tests/test_translator_part2.py b/python_pkg/word_frequency/tests/test_translator_part2.py index c003930..81caac5 100644 --- a/python_pkg/word_frequency/tests/test_translator_part2.py +++ b/python_pkg/word_frequency/tests/test_translator_part2.py @@ -2,6 +2,7 @@ from __future__ import annotations +import tempfile from typing import TYPE_CHECKING from unittest.mock import MagicMock, patch @@ -30,7 +31,7 @@ if TYPE_CHECKING: class TestGetInstalledLanguages: """Tests for get_installed_languages function.""" - def test_argos_unavailable(self, _mock_argos_unavailable: None) -> None: + def test_argos_unavailable(self, mock_argos_unavailable: None) -> None: """Test when argos is unavailable.""" result = get_installed_languages() assert result == [] @@ -56,7 +57,7 @@ class TestGetInstalledLanguages: mock_parent.package = mock_package_module with ( - patch.object(translator, "_check_argos", return_value=True), + patch.object(translator, "check_argos", return_value=True), patch.object(translator, "argostranslate", mock_parent, create=True), patch.dict( "sys.modules", @@ -79,7 +80,7 @@ class TestGetInstalledLanguages: class TestGetAvailablePackages: """Tests for get_available_packages function.""" - def test_argos_unavailable(self, _mock_argos_unavailable: None) -> None: + def test_argos_unavailable(self, mock_argos_unavailable: None) -> None: """Test when argos is unavailable.""" result = get_available_packages() assert result == [] @@ -91,7 +92,7 @@ class TestGetAvailablePackages: class TestDownloadLanguages: """Tests for download_languages function.""" - def test_argos_unavailable(self, _mock_argos_unavailable: None) -> None: + def test_argos_unavailable(self, mock_argos_unavailable: None) -> None: """Test when argos is unavailable.""" result = download_languages(["en", "es"]) assert result == {} @@ -124,7 +125,7 @@ class TestReadFile: class TestMain: """Tests for main CLI function.""" - def test_argos_unavailable_error(self, _mock_argos_unavailable: None) -> None: + def test_argos_unavailable_error(self, mock_argos_unavailable: None) -> None: """Test error when argos not installed.""" result = main(["--text", "hello", "--from", "en", "--to", "es"]) assert result == 1 @@ -139,7 +140,7 @@ class TestMain: mock_parent.package = mock_package_module with ( - patch.object(translator, "_check_argos", return_value=True), + patch.object(translator, "check_argos", return_value=True), patch.object(translator, "argostranslate", mock_parent, create=True), patch.dict( "sys.modules", @@ -172,7 +173,7 @@ class TestMain: mock_parent.package = mock_package_module with ( - patch.object(translator, "_check_argos", return_value=True), + patch.object(translator, "check_argos", return_value=True), patch.object(translator, "argostranslate", mock_parent, create=True), patch.dict( "sys.modules", @@ -326,7 +327,7 @@ class TestGetAvailablePackagesWithArgos: mock_parent.translate = mock_translate with ( - patch.object(translator, "_check_argos", return_value=True), + patch.object(translator, "check_argos", return_value=True), patch.object(translator, "argostranslate", mock_parent, create=True), patch.dict( "sys.modules", @@ -348,7 +349,7 @@ class TestDownloadLanguagesFull: pkg = MagicMock() pkg.from_code = "en" pkg.to_code = "es" - pkg.download.return_value = "/tmp/fake.argosmodel" + pkg.download.return_value = tempfile.gettempdir() + "/fake.argosmodel" mock_package = MagicMock() mock_package.update_package_index.return_value = None @@ -359,7 +360,7 @@ class TestDownloadLanguagesFull: mock_parent.translate = mock_translate with ( - patch.object(translator, "_check_argos", return_value=True), + patch.object(translator, "check_argos", return_value=True), patch.object(translator, "argostranslate", mock_parent, create=True), patch.dict( "sys.modules", @@ -384,7 +385,7 @@ class TestDownloadLanguagesFull: mock_parent.translate = mock_translate with ( - patch.object(translator, "_check_argos", return_value=True), + patch.object(translator, "check_argos", return_value=True), patch.object(translator, "argostranslate", mock_parent, create=True), patch.dict( "sys.modules", @@ -414,7 +415,7 @@ class TestDownloadLanguagesFull: mock_parent.translate = mock_translate with ( - patch.object(translator, "_check_argos", return_value=True), + patch.object(translator, "check_argos", return_value=True), patch.object(translator, "argostranslate", mock_parent, create=True), patch.dict( "sys.modules", diff --git a/python_pkg/word_frequency/translator.py b/python_pkg/word_frequency/translator.py index e614921..fc078fc 100755 --- a/python_pkg/word_frequency/translator.py +++ b/python_pkg/word_frequency/translator.py @@ -65,7 +65,7 @@ logger = logging.getLogger(__name__) _BATCH_SIZE = 100 -def _check_argos() -> bool: +def check_argos() -> bool: """Check if argostranslate is available.""" return argostranslate is not None @@ -76,7 +76,7 @@ def get_installed_languages() -> list[tuple[str, str]]: Returns: List of (code, name) tuples for installed languages. """ - if not _check_argos(): + if not check_argos(): return [] languages = argostranslate.translate.get_installed_languages() @@ -89,7 +89,7 @@ def get_available_packages() -> list[tuple[str, str, str, str]]: Returns: List of (from_code, from_name, to_code, to_name) tuples. """ - if not _check_argos(): + if not check_argos(): return [] argostranslate.package.update_package_index() @@ -111,7 +111,7 @@ def download_languages(lang_codes: Sequence[str]) -> dict[str, bool]: Returns: Dict mapping "from->to" to success boolean. """ - if not _check_argos(): + if not check_argos(): return {} results: dict[str, bool] = {}