testsAndMisc/python_pkg/brother_printer/tests/test_cups_service_part4.py
Krzysztof kuhy Rudnicki 2545d72710 test: achieve 100% branch coverage across all python_pkg packages
- Add comprehensive tests for all packages (3572 tests, 100% branch coverage)
- Split oversized test files to stay under 500-line limit
- Add per-file ruff ignores for test-appropriate suppressions
- Fix _cache_decks.py to properly convert JSON lists to tuples
- Add session-scoped conftest fixture for logging handler cleanup (Python 3.14)
- Update ruff pre-commit hook to v0.15.2
- Add codespell ignore words for test data
- Add generated output files to .gitignore
2026-03-21 17:51:36 +01:00

87 lines
3.4 KiB
Python

"""Tests for brother_printer.cups_service module - part 4 (consumable life, IPP)."""
from __future__ import annotations
import subprocess
from unittest.mock import MagicMock, patch
from python_pkg.brother_printer.cups_service import (
_get_cups_ipp_status,
_parse_ipp_attributes,
estimate_consumable_life,
)
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:
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:
mock_load.return_value = {"toner_replaced_at": 0, "drum_replaced_at": 0}
result = estimate_consumable_life()
assert result.total_pages == 500
assert result.toner_pct_remaining == 50
assert result.toner_exhausted is False
assert result.toner_low is False
@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:
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:
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:
mock_load.return_value = {"toner_replaced_at": 8500, "drum_replaced_at": 0}
result = estimate_consumable_life()
assert result.drum_near_end is True
class TestParseIppAttributes:
def test_parse(self) -> None:
output = " printer-state (enum) = idle\n printer-name (name) = Brother\n"
result = _parse_ipp_attributes(output)
assert result["printer-state"] == "idle"
assert result["printer-name"] == "Brother"
def test_no_match(self) -> None:
result = _parse_ipp_attributes("no attributes here\n")
assert result == {}
class TestGetCupsIppStatus:
@patch(f"{MOD}.shutil.which", return_value=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:
mock_run.return_value = MagicMock(
stdout=" printer-state (enum) = idle\n",
)
result = _get_cups_ipp_status("Brother")
assert result["printer-state"] == "idle"
@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:
mock_run.side_effect = subprocess.TimeoutExpired("ipptool", 10)
assert _get_cups_ipp_status("Brother") == {}