mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-12 14:30:57 +01:00
cups_service.py 406 -> 234. The IPP attribute queries, the CUPS status-code mapping, find_cups_printer_name and query_usb_via_cups move to _cups_fallback.py; cups_service keeps service control and raw USB port status. The seam is not the section header: test_cups_service_part2.py imports _get_cups_economode and find_cups_printer_name together and patches shutil once, so those two had to travel as a pair. test_cups_service.py, the largest module, needs no edit. usb_query.py reaches the fallback through a late import_module, so both that string and the absolute patch target in test_usb_query.py moved with it. Pass count is 402 before and after. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
262 lines
10 KiB
Python
262 lines
10 KiB
Python
"""Tests for brother_printer._cups_fallback module - part 2."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from python_pkg.brother_printer._cups_fallback import (
|
|
_cups_reasons_to_error,
|
|
_get_cups_economode,
|
|
_map_cups_to_status_code,
|
|
_port_status_to_status_code,
|
|
find_cups_printer_name,
|
|
)
|
|
from python_pkg.brother_printer.constants import (
|
|
_CUPS_REASONS_TO_STATUS,
|
|
DERIVED_CUPS_ERROR,
|
|
get_status_info,
|
|
)
|
|
from python_pkg.brother_printer.data_classes import (
|
|
USBPortStatus,
|
|
)
|
|
|
|
MOD = "python_pkg.brother_printer._cups_fallback"
|
|
|
|
|
|
# ── _get_cups_economode ──────────────────────────────────────────────
|
|
|
|
|
|
class TestGetCupsEconomode:
|
|
"""Tests for _get_cups_economode."""
|
|
|
|
@patch(f"{MOD}.shutil.which", return_value=None)
|
|
def test_no_lpoptions(self, m: MagicMock) -> None:
|
|
assert _get_cups_economode("Brother") == ""
|
|
|
|
@patch("python_pkg.brother_printer._query.subprocess.run")
|
|
@patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpoptions")
|
|
def test_economode_on(self, w: MagicMock, mock_run: MagicMock) -> None:
|
|
mock_run.return_value = MagicMock(
|
|
stdout="BREconomode/Toner Save Mode: *True False\n"
|
|
)
|
|
assert _get_cups_economode("Brother") == "ON"
|
|
|
|
@patch("python_pkg.brother_printer._query.subprocess.run")
|
|
@patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpoptions")
|
|
def test_economode_off(self, w: MagicMock, mock_run: MagicMock) -> None:
|
|
mock_run.return_value = MagicMock(
|
|
stdout="BREconomode/Toner Save Mode: True *False\n"
|
|
)
|
|
assert _get_cups_economode("Brother") == "OFF"
|
|
|
|
@patch("python_pkg.brother_printer._query.subprocess.run")
|
|
@patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpoptions")
|
|
def test_no_economode_line(self, w: MagicMock, mock_run: MagicMock) -> None:
|
|
mock_run.return_value = MagicMock(
|
|
stdout="Resolution/Output Resolution: 600dpi *1200dpi\n"
|
|
)
|
|
assert _get_cups_economode("Brother") == ""
|
|
|
|
@patch("python_pkg.brother_printer._query.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:
|
|
mock_run.return_value = MagicMock(
|
|
stdout="BREconomode/Toner Save Mode: True False\n"
|
|
)
|
|
assert _get_cups_economode("Brother") == ""
|
|
|
|
@patch("python_pkg.brother_printer._query.subprocess.run")
|
|
@patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpoptions")
|
|
def test_timeout(self, w: MagicMock, mock_run: MagicMock) -> None:
|
|
mock_run.side_effect = subprocess.TimeoutExpired("lpoptions", 5)
|
|
assert _get_cups_economode("Brother") == ""
|
|
|
|
@patch("python_pkg.brother_printer._query.subprocess.run")
|
|
@patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpoptions")
|
|
def test_oserror(self, w: MagicMock, mock_run: MagicMock) -> None:
|
|
mock_run.side_effect = OSError("fail")
|
|
assert _get_cups_economode("Brother") == ""
|
|
|
|
|
|
# ── _map_cups_to_status_code ─────────────────────────────────────────
|
|
|
|
|
|
class TestMapCupsToStatusCode:
|
|
"""Tests for _map_cups_to_status_code.
|
|
|
|
Every code here must exist in BROTHER_STATUS_CODES; mapping CUPS onto a
|
|
code the table does not know silently degrades to "unknown status".
|
|
"""
|
|
|
|
def test_reason_match(self) -> None:
|
|
assert _map_cups_to_status_code("idle", "toner-low-report") == "40038"
|
|
|
|
def test_state_match(self) -> None:
|
|
assert _map_cups_to_status_code("idle", "none") == "10001"
|
|
|
|
def test_processing_state(self) -> None:
|
|
assert _map_cups_to_status_code("processing", "none") == "10023"
|
|
|
|
def test_stopped_state(self) -> None:
|
|
assert _map_cups_to_status_code("stopped", "none") == "40079"
|
|
|
|
def test_unknown_state(self) -> None:
|
|
assert _map_cups_to_status_code("mystery", "none") == "10001"
|
|
|
|
def test_state_with_parenthetical(self) -> None:
|
|
assert _map_cups_to_status_code("idle (on fire)", "none") == "10001"
|
|
|
|
def test_every_mapped_code_is_resolvable(self) -> None:
|
|
"""Guard the class of bug where a mapping points at a deleted code."""
|
|
for state in ("idle", "processing", "stopped"):
|
|
code = _map_cups_to_status_code(state, "none")
|
|
_, text, _ = get_status_info(code)
|
|
assert "Unknown" not in text
|
|
for reason in _CUPS_REASONS_TO_STATUS:
|
|
code = _map_cups_to_status_code("idle", reason)
|
|
_, text, _ = get_status_info(code)
|
|
assert "Unknown" not in text
|
|
|
|
|
|
# ── _cups_reasons_to_error ───────────────────────────────────────────
|
|
|
|
|
|
class TestCupsReasonsToError:
|
|
"""Tests for _cups_reasons_to_error."""
|
|
|
|
def test_media_jam(self) -> None:
|
|
"""A real jam is 40022. It used to map to 40000, which is Sleep."""
|
|
code, display = _cups_reasons_to_error("media-jam-report")
|
|
assert code == "40022"
|
|
assert display == "Paper Jam"
|
|
|
|
def test_cover_open(self) -> None:
|
|
code, _ = _cups_reasons_to_error("cover-open")
|
|
assert code == "40021"
|
|
|
|
def test_door_open(self) -> None:
|
|
code, _ = _cups_reasons_to_error("door-open")
|
|
assert code == "40021"
|
|
|
|
def test_toner_empty(self) -> None:
|
|
code, _ = _cups_reasons_to_error("toner-empty")
|
|
assert code == "40010"
|
|
|
|
def test_toner_low(self) -> None:
|
|
code, _ = _cups_reasons_to_error("toner-low")
|
|
assert code == "40038"
|
|
|
|
def test_unknown_reason_shows_what_cups_said(self) -> None:
|
|
"""'Printer Error' alone is useless; quote the reason we were given."""
|
|
code, display = _cups_reasons_to_error("something-weird")
|
|
assert code == DERIVED_CUPS_ERROR
|
|
assert "something-weird" in display
|
|
|
|
def test_no_reason_at_all(self) -> None:
|
|
code, display = _cups_reasons_to_error("none")
|
|
assert code == DERIVED_CUPS_ERROR
|
|
assert "no reason" in display.lower()
|
|
|
|
def test_every_error_code_is_resolvable(self) -> None:
|
|
for reason in ("media-jam", "cover-open", "toner-empty", "toner-low"):
|
|
code, _ = _cups_reasons_to_error(reason)
|
|
_, text, _ = get_status_info(code)
|
|
assert "Unknown" not in text
|
|
|
|
|
|
# ── _port_status_to_status_code ──────────────────────────────────────
|
|
|
|
|
|
class TestPortStatusToStatusCode:
|
|
"""Tests for _port_status_to_status_code.
|
|
|
|
The port status only exposes paper/error/online bits, so it must not
|
|
pretend to know more than that - it previously reported "Cover Open" for
|
|
any error on an offline printer, which was a guess.
|
|
"""
|
|
|
|
def test_error_and_paper_empty(self) -> None:
|
|
ps = USBPortStatus(error=True, paper_empty=True, online=True)
|
|
code, display = _port_status_to_status_code(ps, "none")
|
|
assert code == "41000"
|
|
assert display == "No Paper"
|
|
|
|
def test_error_and_not_online_defers_to_cups(self) -> None:
|
|
ps = USBPortStatus(error=True, paper_empty=False, online=False)
|
|
code, display = _port_status_to_status_code(ps, "cover-open")
|
|
assert code == "40021"
|
|
assert display == "Cover Open"
|
|
|
|
def test_error_with_no_clue_does_not_guess(self) -> None:
|
|
ps = USBPortStatus(error=True, paper_empty=False, online=False)
|
|
code, _ = _port_status_to_status_code(ps, "none")
|
|
assert code == DERIVED_CUPS_ERROR
|
|
|
|
def test_error_only(self) -> None:
|
|
ps = USBPortStatus(error=True, paper_empty=False, online=True)
|
|
code, _ = _port_status_to_status_code(ps, "media-jam")
|
|
assert code == "40022"
|
|
|
|
def test_paper_empty_no_error(self) -> None:
|
|
ps = USBPortStatus(error=False, paper_empty=True, online=True)
|
|
code, _ = _port_status_to_status_code(ps, "none")
|
|
assert code == "41000"
|
|
|
|
def test_not_online_no_error(self) -> None:
|
|
ps = USBPortStatus(error=False, paper_empty=False, online=False)
|
|
code, display = _port_status_to_status_code(ps, "none")
|
|
assert code == "10002"
|
|
assert display == "Offline / Sleep"
|
|
|
|
def test_all_ok(self) -> None:
|
|
ps = USBPortStatus(error=False, paper_empty=False, online=True)
|
|
code, display = _port_status_to_status_code(ps, "none")
|
|
assert code == ""
|
|
assert display == ""
|
|
|
|
|
|
# ── find_cups_printer_name ───────────────────────────────────────────
|
|
|
|
|
|
class TestFindCupsPrinterName:
|
|
"""Tests for find_cups_printer_name."""
|
|
|
|
@patch(f"{MOD}.shutil.which", return_value=None)
|
|
def test_no_lpstat(self, m: MagicMock) -> None:
|
|
assert find_cups_printer_name() == ""
|
|
|
|
@patch("python_pkg.brother_printer._query.subprocess.run")
|
|
@patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpstat")
|
|
def test_found(self, w: MagicMock, mock_run: MagicMock) -> None:
|
|
mock_run.return_value = MagicMock(
|
|
stdout="device for BrotherHL1110: usb://Brother/HL-1110\n"
|
|
)
|
|
assert find_cups_printer_name() == "BrotherHL1110"
|
|
|
|
@patch("python_pkg.brother_printer._query.subprocess.run")
|
|
@patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpstat")
|
|
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("python_pkg.brother_printer._query.subprocess.run")
|
|
@patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpstat")
|
|
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"
|
|
)
|
|
assert find_cups_printer_name() == ""
|
|
|
|
@patch("python_pkg.brother_printer._query.subprocess.run")
|
|
@patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpstat")
|
|
def test_timeout(self, w: MagicMock, mock_run: MagicMock) -> None:
|
|
mock_run.side_effect = subprocess.TimeoutExpired("lpstat", 5)
|
|
assert find_cups_printer_name() == ""
|
|
|
|
@patch("python_pkg.brother_printer._query.subprocess.run")
|
|
@patch(f"{MOD}.shutil.which", return_value="/usr/bin/lpstat")
|
|
def test_oserror(self, w: MagicMock, mock_run: MagicMock) -> None:
|
|
mock_run.side_effect = OSError("fail")
|
|
assert find_cups_printer_name() == ""
|