testsAndMisc-archive/python_pkg/puzzle_solver/tests/test_main.py
Krzysztof kuhy Rudnicki e5fd82c822 Reduce per-file-ignores by fixing lint violations across codebase
Fix ruff violations in ~15 source files and ~60+ test files to minimize
per-file-ignores in pyproject.toml. Remaining ignores are justified with
comments explaining why each suppression is necessary.

Source fixes: FBT003 (keyword args), S310 (URL validation), SLF001
(private access), T201 (print→logging), C901 (complexity), E501 (line
length), E402 (import order).

Test fixes: SIM117 (combined with), FBT (boolean args), PERF203 (try in
loop), S310/S607 (URLs/executables), E402/E501 (imports/lines), S108
(tmp paths), PLR0913 (too many args), ARG (unused args), ANN (type
annotations), RUF059 (unused unpacked vars), PT019 (fixture naming).

Remaining per-file-ignores (with justifications):
- Tests: ARG, D, PLC0415, PLR2004, S101, SLF001
- music_gen sources: PLC0415 (heavy ML lazy imports)
- moviepy_showcase: PLC0415 (circular dependency)
- generate_images: PLR0913 (matplotlib helpers need many params)
- praca_magisterska_video: E501, E402 (long paths, mpl.use)
2026-03-25 18:58:05 +01:00

348 lines
12 KiB
Python

"""Tests for python_pkg.puzzle_solver.main and __main__ modules."""
from __future__ import annotations
import json
from typing import Any
from unittest.mock import MagicMock, mock_open, patch
import pytest
from python_pkg.puzzle_solver.main import (
cmd_debug,
cmd_parse,
cmd_run,
cmd_solve,
main,
)
def _minimal_puzzle_data() -> dict[str, Any]:
return {
"squares": [
{"pos": [0, 0], "type": "player"},
{"pos": [0, 1], "type": "normal"},
{"pos": [0, 2], "type": "goal"},
],
}
# ── cmd_parse ────────────────────────────────────────────────────────
class TestCmdParse:
@patch("python_pkg.puzzle_solver.main.save_puzzle")
@patch("python_pkg.puzzle_solver.main.parse_image")
def test_with_output(self, mock_parse: MagicMock, mock_save: MagicMock) -> None:
mock_parse.return_value = {"squares": [], "notes": []}
args = MagicMock()
args.image = "test.png"
args.output = "out.json"
args.threshold = 55
cmd_parse(args)
mock_save.assert_called_once_with({"squares": [], "notes": []}, "out.json")
@patch("python_pkg.puzzle_solver.main.save_puzzle")
@patch("python_pkg.puzzle_solver.main.parse_image")
def test_default_output(self, mock_parse: MagicMock, mock_save: MagicMock) -> None:
mock_parse.return_value = {"squares": [], "notes": []}
args = MagicMock()
args.image = "screenshot.png"
args.output = None
args.threshold = 55
cmd_parse(args)
mock_save.assert_called_once_with(
{"squares": [], "notes": []}, "screenshot_puzzle.json"
)
@patch("python_pkg.puzzle_solver.main.save_puzzle")
@patch("python_pkg.puzzle_solver.main.parse_image")
def test_with_notes(self, mock_parse: MagicMock, mock_save: MagicMock) -> None:
mock_parse.return_value = {
"squares": [],
"notes": ["note1", "note2"],
}
args = MagicMock()
args.image = "test.png"
args.output = "out.json"
args.threshold = 55
cmd_parse(args)
@patch("python_pkg.puzzle_solver.main.save_puzzle")
@patch("python_pkg.puzzle_solver.main.parse_image")
def test_no_notes(self, mock_parse: MagicMock, mock_save: MagicMock) -> None:
mock_parse.return_value = {"squares": []}
args = MagicMock()
args.image = "test.png"
args.output = "out.json"
args.threshold = 55
cmd_parse(args)
# ── cmd_solve ────────────────────────────────────────────────────────
class TestCmdSolve:
@patch("python_pkg.puzzle_solver.main.print_solution")
@patch("python_pkg.puzzle_solver.main.solve")
@patch("python_pkg.puzzle_solver.main.print_puzzle")
@patch("python_pkg.puzzle_solver.main.Puzzle")
def test_solvable(
self,
mock_puzzle_cls: MagicMock,
mock_print: MagicMock,
mock_solve: MagicMock,
mock_print_sol: MagicMock,
) -> None:
data = _minimal_puzzle_data()
m = mock_open(read_data=json.dumps(data))
mock_puzzle = MagicMock()
mock_puzzle_cls.from_json.return_value = mock_puzzle
mock_solve.return_value = ["right"]
with patch("pathlib.Path.open", m):
args = MagicMock()
args.puzzle = "test.json"
cmd_solve(args)
mock_print_sol.assert_called_once()
@patch("python_pkg.puzzle_solver.main.solve")
@patch("python_pkg.puzzle_solver.main.print_puzzle")
@patch("python_pkg.puzzle_solver.main.Puzzle")
def test_unsolvable(
self,
mock_puzzle_cls: MagicMock,
mock_print: MagicMock,
mock_solve: MagicMock,
) -> None:
data = _minimal_puzzle_data()
m = mock_open(read_data=json.dumps(data))
mock_puzzle = MagicMock()
mock_puzzle_cls.from_json.return_value = mock_puzzle
mock_solve.return_value = None
args = MagicMock()
args.puzzle = "test.json"
with patch("pathlib.Path.open", m), pytest.raises(SystemExit):
cmd_solve(args)
# ── cmd_run ──────────────────────────────────────────────────────────
class TestCmdRun:
@patch("python_pkg.puzzle_solver.main.print_solution")
@patch("python_pkg.puzzle_solver.main.solve")
@patch("python_pkg.puzzle_solver.main.print_puzzle")
@patch("python_pkg.puzzle_solver.main.Puzzle")
@patch("python_pkg.puzzle_solver.main.parse_image")
def test_solvable(
self,
mock_parse: MagicMock,
mock_puzzle_cls: MagicMock,
mock_print: MagicMock,
mock_solve: MagicMock,
mock_print_sol: MagicMock,
) -> None:
mock_parse.return_value = _minimal_puzzle_data()
mock_puzzle = MagicMock()
mock_puzzle_cls.from_json.return_value = mock_puzzle
mock_solve.return_value = ["right"]
args = MagicMock()
args.image = "test.png"
args.threshold = 55
cmd_run(args)
mock_print_sol.assert_called_once()
@patch("python_pkg.puzzle_solver.main.save_puzzle")
@patch("python_pkg.puzzle_solver.main.solve")
@patch("python_pkg.puzzle_solver.main.print_puzzle")
@patch("python_pkg.puzzle_solver.main.Puzzle")
@patch("python_pkg.puzzle_solver.main.parse_image")
def test_unsolvable(
self,
mock_parse: MagicMock,
mock_puzzle_cls: MagicMock,
mock_print: MagicMock,
mock_solve: MagicMock,
mock_save: MagicMock,
) -> None:
mock_parse.return_value = _minimal_puzzle_data()
mock_puzzle = MagicMock()
mock_puzzle_cls.from_json.return_value = mock_puzzle
mock_solve.return_value = None
args = MagicMock()
args.image = "test.png"
args.threshold = 55
with pytest.raises(SystemExit):
cmd_run(args)
mock_save.assert_called_once()
@patch("python_pkg.puzzle_solver.main.print_solution")
@patch("python_pkg.puzzle_solver.main.solve")
@patch("python_pkg.puzzle_solver.main.print_puzzle")
@patch("python_pkg.puzzle_solver.main.Puzzle")
@patch("python_pkg.puzzle_solver.main.parse_image")
def test_with_notes(
self,
mock_parse: MagicMock,
mock_puzzle_cls: MagicMock,
mock_print: MagicMock,
mock_solve: MagicMock,
mock_print_sol: MagicMock,
) -> None:
data = _minimal_puzzle_data()
data["notes"] = ["note1"]
mock_parse.return_value = data
mock_puzzle = MagicMock()
mock_puzzle_cls.from_json.return_value = mock_puzzle
mock_solve.return_value = ["right"]
args = MagicMock()
args.image = "test.png"
args.threshold = 55
cmd_run(args)
@patch("python_pkg.puzzle_solver.main.print_solution")
@patch("python_pkg.puzzle_solver.main.solve")
@patch("python_pkg.puzzle_solver.main.print_puzzle")
@patch("python_pkg.puzzle_solver.main.Puzzle")
@patch("python_pkg.puzzle_solver.main.parse_image")
def test_no_notes_key(
self,
mock_parse: MagicMock,
mock_puzzle_cls: MagicMock,
mock_print: MagicMock,
mock_solve: MagicMock,
mock_print_sol: MagicMock,
) -> None:
data = _minimal_puzzle_data()
# no "notes" key at all
mock_parse.return_value = data
mock_puzzle = MagicMock()
mock_puzzle_cls.from_json.return_value = mock_puzzle
mock_solve.return_value = ["right"]
args = MagicMock()
args.image = "test.png"
args.threshold = 55
cmd_run(args)
# ── cmd_debug ────────────────────────────────────────────────────────
class TestCmdDebug:
@patch("python_pkg.puzzle_solver.main.draw_debug")
@patch("python_pkg.puzzle_solver.main.parse_image")
def test_with_output(self, mock_parse: MagicMock, mock_draw: MagicMock) -> None:
mock_parse.return_value = {
"squares": [
{"type": "normal"},
{"type": "normal"},
{"type": "goal"},
],
}
args = MagicMock()
args.image = "test.png"
args.output = "debug.png"
args.threshold = 55
cmd_debug(args)
mock_draw.assert_called_once_with(
"test.png", mock_parse.return_value, "debug.png"
)
@patch("python_pkg.puzzle_solver.main.draw_debug")
@patch("python_pkg.puzzle_solver.main.parse_image")
def test_default_output(self, mock_parse: MagicMock, mock_draw: MagicMock) -> None:
mock_parse.return_value = {
"squares": [{"type": "normal"}],
}
args = MagicMock()
args.image = "screenshot.png"
args.output = None
args.threshold = 55
cmd_debug(args)
mock_draw.assert_called_once_with(
"screenshot.png", mock_parse.return_value, "screenshot_debug.png"
)
# ── main ─────────────────────────────────────────────────────────────
class TestMain:
@patch("python_pkg.puzzle_solver.main.parse_image")
@patch("python_pkg.puzzle_solver.main.save_puzzle")
def test_parse_command(self, mock_save: MagicMock, mock_parse: MagicMock) -> None:
mock_parse.return_value = {"squares": [], "notes": []}
with patch("sys.argv", ["prog", "parse", "img.png", "-o", "out.json"]):
main()
@patch("python_pkg.puzzle_solver.main.print_solution")
@patch("python_pkg.puzzle_solver.main.solve")
@patch("python_pkg.puzzle_solver.main.print_puzzle")
@patch("python_pkg.puzzle_solver.main.Puzzle")
def test_solve_command(
self,
mock_puzzle_cls: MagicMock,
mock_print: MagicMock,
mock_solve: MagicMock,
mock_print_sol: MagicMock,
) -> None:
data = _minimal_puzzle_data()
m = mock_open(read_data=json.dumps(data))
mock_puzzle = MagicMock()
mock_puzzle_cls.from_json.return_value = mock_puzzle
mock_solve.return_value = ["right"]
with (
patch("pathlib.Path.open", m),
patch("sys.argv", ["prog", "solve", "puzzle.json"]),
):
main()
@patch("python_pkg.puzzle_solver.main.print_solution")
@patch("python_pkg.puzzle_solver.main.solve")
@patch("python_pkg.puzzle_solver.main.print_puzzle")
@patch("python_pkg.puzzle_solver.main.Puzzle")
@patch("python_pkg.puzzle_solver.main.parse_image")
def test_run_command(
self,
mock_parse: MagicMock,
mock_puzzle_cls: MagicMock,
mock_print: MagicMock,
mock_solve: MagicMock,
mock_print_sol: MagicMock,
) -> None:
mock_parse.return_value = _minimal_puzzle_data()
mock_puzzle = MagicMock()
mock_puzzle_cls.from_json.return_value = mock_puzzle
mock_solve.return_value = ["right"]
with patch("sys.argv", ["prog", "run", "img.png"]):
main()
@patch("python_pkg.puzzle_solver.main.draw_debug")
@patch("python_pkg.puzzle_solver.main.parse_image")
def test_debug_command(self, mock_parse: MagicMock, mock_draw: MagicMock) -> None:
mock_parse.return_value = {"squares": [{"type": "normal"}]}
with patch("sys.argv", ["prog", "debug", "img.png", "-o", "d.png"]):
main()
# ── __main__.py ──────────────────────────────────────────────────────
class TestDunderMain:
@patch("python_pkg.puzzle_solver.main.main")
def test_main_called(self, mock_main: MagicMock) -> None:
import importlib
import python_pkg.puzzle_solver.__main__ as mod
importlib.reload(mod)
mock_main.assert_called()