Skip to content

Commit b4f247d

Browse files
committed
chore: fix typing in tests
1 parent 7f1084a commit b4f247d

7 files changed

Lines changed: 446 additions & 79 deletions

File tree

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,5 @@ repos:
5757
hooks:
5858
- id: mypy
5959
exclude: cli.py
60-
additional_dependencies: [ "pydantic>=2.0.0", "pydantic-xml>=2.18.0", "pytest>=8.0.0" ]
61-
args: [ "--config-file=./pyproject.toml", "--follow-imports=silent", "--strict", "--ignore-missing-imports", "--disallow-subclassing-any", "--no-warn-return-any" ]
60+
additional_dependencies: [ "pydantic>=2.0.0", "pytest>=8.0.0" ]
61+
args: [ "--config-file=./pyproject.toml"]

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"--cov-report=xml:coverage.xml",
99
"tests"
1010
],
11-
"mypy-type-checker.importStrategy": "fromEnvironment",
11+
// "mypy-type-checker.importStrategy": "fromEnvironment",
1212
"python.analysis.typeCheckingMode": "basic",
1313
"[python]": {
1414
"editor.defaultFormatter": "charliermarsh.ruff",

pyproject.toml

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ license = {text = "Apache-2.0"}
1313
dependencies = [
1414
"pydantic >=2.0.0,<3.0.0",
1515
"click >=8.0.0,<8.4.0",
16-
"xmltodict >=1.0.2,<2.0.0",
16+
"xmltodict >=1.0.1,<2.0.0",
1717
]
1818

1919
[project.scripts]
@@ -24,12 +24,22 @@ cli = [
2424
"scapy>=2.6.1,<3.0.0",
2525
]
2626

27+
[dependency-groups]
28+
dev = [
29+
"pre-commit>=4.0.0,<5.0.0",
30+
"mypy>=1.18.2,<2.0.0",
31+
"types-xmltodict >=1.0.1,<2.0.0",
32+
"pytest>=8.0.0,<9.0.0",
33+
"pytest-cov>=7.0.0,<8.0.0",
34+
"pytest-asyncio>=1.2.0,<2.0.0",
35+
"pytest-subtests>=0.15.0,<1.0.0",
36+
]
37+
2738
[tool.mypy]
2839
python_version = "3.13"
29-
plugins = [
30-
"pydantic.mypy",
31-
"pydantic_xml.mypy",
32-
]
40+
# plugins = [
41+
# "pydantic.mypy"
42+
# ]
3343
follow_imports = "silent"
3444
strict = true
3545
ignore_missing_imports = true

tests/test_api.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
# type: ignore
2-
31
"""Comprehensive tests for the OmniLogic API layer.
42
53
Focuses on:
@@ -11,7 +9,7 @@
119

1210
from __future__ import annotations
1311

14-
from typing import TYPE_CHECKING
12+
from typing import TYPE_CHECKING, Any
1513
from unittest.mock import AsyncMock, MagicMock, patch
1614
from xml.etree import ElementTree as ET
1715

@@ -20,7 +18,7 @@
2018
from pyomnilogic_local.api.api import OmniLogicAPI, _validate_id, _validate_speed, _validate_temperature
2119
from pyomnilogic_local.api.constants import MAX_SPEED_PERCENT, MAX_TEMPERATURE_F, MIN_SPEED_PERCENT, MIN_TEMPERATURE_F, XML_NAMESPACE
2220
from pyomnilogic_local.api.exceptions import OmniValidationError
23-
from pyomnilogic_local.omnitypes import ColorLogicBrightness, ColorLogicShow40, ColorLogicSpeed, HeaterMode
21+
from pyomnilogic_local.omnitypes import ColorLogicBrightness, ColorLogicShow40, ColorLogicSpeed, HeaterMode, MessageType
2422

2523
if TYPE_CHECKING:
2624
from pytest_subtests import SubTests
@@ -60,7 +58,7 @@ def _find_param(root: ET.Element, name: str) -> ET.Element:
6058

6159
def test_validate_temperature(subtests: SubTests) -> None:
6260
"""Test temperature validation with various inputs using table-driven approach."""
63-
test_cases = [
61+
test_cases: list[tuple[Any, str, bool, str]] = [
6462
# (temperature, param_name, should_pass, description) # noqa: ERA001
6563
(MIN_TEMPERATURE_F, "temp", True, "minimum valid temperature"),
6664
(MAX_TEMPERATURE_F, "temp", True, "maximum valid temperature"),
@@ -83,7 +81,7 @@ def test_validate_temperature(subtests: SubTests) -> None:
8381

8482
def test_validate_speed(subtests: SubTests) -> None:
8583
"""Test speed validation with various inputs using table-driven approach."""
86-
test_cases = [
84+
test_cases: list[tuple[Any, str, bool, str]] = [
8785
# (speed, param_name, should_pass, description) # noqa: ERA001
8886
(MIN_SPEED_PERCENT, "speed", True, "minimum valid speed (0)"),
8987
(MAX_SPEED_PERCENT, "speed", True, "maximum valid speed (100)"),
@@ -106,7 +104,7 @@ def test_validate_speed(subtests: SubTests) -> None:
106104

107105
def test_validate_id(subtests: SubTests) -> None:
108106
"""Test ID validation with various inputs using table-driven approach."""
109-
test_cases = [
107+
test_cases: list[tuple[Any, str, bool, str]] = [
110108
# (id_value, param_name, should_pass, description) # noqa: ERA001
111109
(0, "pool_id", True, "zero ID"),
112110
(1, "pool_id", True, "positive ID"),
@@ -149,7 +147,7 @@ def test_api_init_custom_params() -> None:
149147

150148
def test_api_init_validation(subtests: SubTests) -> None:
151149
"""Test OmniLogicAPI initialization validation using table-driven approach."""
152-
test_cases = [
150+
test_cases: list[tuple[Any, Any, Any, bool, str]] = [
153151
# (ip, port, timeout, should_pass, description) # noqa: ERA001
154152
("", 10444, 5.0, False, "empty IP address"),
155153
("192.168.1.100", 0, 5.0, False, "zero port"),
@@ -489,7 +487,7 @@ async def test_async_send_message_creates_transport() -> None:
489487
with patch("asyncio.get_running_loop") as mock_loop:
490488
mock_loop.return_value.create_datagram_endpoint = AsyncMock(return_value=(mock_transport, mock_protocol))
491489

492-
await api.async_send_message(0x01, "test", need_response=False)
490+
await api.async_send_message(MessageType.REQUEST_CONFIGURATION, "test", need_response=False)
493491

494492
# Verify endpoint was created with correct parameters
495493
mock_loop.return_value.create_datagram_endpoint.assert_called_once()
@@ -512,7 +510,7 @@ async def test_async_send_message_with_response() -> None:
512510
with patch("asyncio.get_running_loop") as mock_loop:
513511
mock_loop.return_value.create_datagram_endpoint = AsyncMock(return_value=(mock_transport, mock_protocol))
514512

515-
result = await api.async_send_message(0x01, "test", need_response=True)
513+
result = await api.async_send_message(MessageType.REQUEST_CONFIGURATION, "test", need_response=True)
516514

517515
assert result == "test response"
518516
mock_protocol.send_and_receive.assert_called_once()
@@ -531,7 +529,7 @@ async def test_async_send_message_without_response() -> None:
531529
with patch("asyncio.get_running_loop") as mock_loop:
532530
mock_loop.return_value.create_datagram_endpoint = AsyncMock(return_value=(mock_transport, mock_protocol))
533531

534-
result = await api.async_send_message(0x01, "test", need_response=False)
532+
result = await api.async_send_message(MessageType.REQUEST_CONFIGURATION, "test", need_response=False) # type: ignore[func-returns-value]
535533

536534
assert result is None
537535
mock_protocol.send_message.assert_called_once()
@@ -551,7 +549,7 @@ async def test_async_send_message_closes_transport_on_error() -> None:
551549
mock_loop.return_value.create_datagram_endpoint = AsyncMock(return_value=(mock_transport, mock_protocol))
552550

553551
with pytest.raises(Exception, match="Test error"):
554-
await api.async_send_message(0x01, "test", need_response=False)
552+
await api.async_send_message(MessageType.REQUEST_CONFIGURATION, "test", need_response=False)
555553

556554
# Verify transport was still closed despite the error
557555
mock_transport.close.assert_called_once()

tests/test_decorators.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# type: ignore
21
"""Tests for equipment control method decorators.
32
43
Focuses on:
@@ -10,6 +9,7 @@
109

1110
from __future__ import annotations
1211

12+
from typing import Any
1313
from unittest.mock import MagicMock
1414

1515
import pytest
@@ -35,8 +35,8 @@ def __init__(self, is_ready: bool = True):
3535
self._omni = MagicMock()
3636
self._omni._telemetry_dirty = False
3737
self.method_called = False
38-
self.method_args = None
39-
self.method_kwargs = None
38+
self.method_args: tuple[Any, ...] | None = None
39+
self.method_kwargs: dict[str, Any] | None = None
4040

4141
@control_method
4242
async def turn_on(self) -> None:
@@ -69,7 +69,7 @@ async def set_complex_operation(self, param1: int, param2: str, flag: bool = Fal
6969

7070

7171
@pytest.mark.asyncio
72-
async def test_control_method_when_ready_executes_function():
72+
async def test_control_method_when_ready_executes_function() -> None:
7373
"""Test that control_method executes the wrapped function when equipment is ready."""
7474
equipment = MockEquipment(is_ready=True)
7575

@@ -79,7 +79,7 @@ async def test_control_method_when_ready_executes_function():
7979

8080

8181
@pytest.mark.asyncio
82-
async def test_control_method_when_not_ready_raises_error():
82+
async def test_control_method_when_not_ready_raises_error() -> None:
8383
"""Test that control_method raises OmniEquipmentNotReadyError when equipment is not ready."""
8484
equipment = MockEquipment(is_ready=False)
8585

@@ -91,7 +91,7 @@ async def test_control_method_when_not_ready_raises_error():
9191

9292

9393
@pytest.mark.asyncio
94-
async def test_control_method_marks_telemetry_dirty():
94+
async def test_control_method_marks_telemetry_dirty() -> None:
9595
"""Test that control_method marks telemetry as dirty after successful execution."""
9696
equipment = MockEquipment(is_ready=True)
9797

@@ -103,7 +103,7 @@ async def test_control_method_marks_telemetry_dirty():
103103

104104

105105
@pytest.mark.asyncio
106-
async def test_control_method_does_not_mark_dirty_if_not_ready():
106+
async def test_control_method_does_not_mark_dirty_if_not_ready() -> None:
107107
"""Test that control_method does not mark state dirty if readiness check fails."""
108108
equipment = MockEquipment(is_ready=False)
109109

@@ -114,7 +114,7 @@ async def test_control_method_does_not_mark_dirty_if_not_ready():
114114

115115

116116
@pytest.mark.asyncio
117-
async def test_control_method_passes_arguments():
117+
async def test_control_method_passes_arguments() -> None:
118118
"""Test that control_method properly passes arguments to wrapped function."""
119119
equipment = MockEquipment(is_ready=True)
120120

@@ -125,7 +125,7 @@ async def test_control_method_passes_arguments():
125125

126126

127127
@pytest.mark.asyncio
128-
async def test_control_method_passes_kwargs():
128+
async def test_control_method_passes_kwargs() -> None:
129129
"""Test that control_method properly passes keyword arguments to wrapped function."""
130130
equipment = MockEquipment(is_ready=True)
131131

@@ -138,7 +138,7 @@ async def test_control_method_passes_kwargs():
138138

139139

140140
@pytest.mark.asyncio
141-
async def test_control_method_error_message_for_different_methods():
141+
async def test_control_method_error_message_for_different_methods() -> None:
142142
"""Test that control_method generates appropriate error messages for different method names."""
143143
equipment = MockEquipment(is_ready=False)
144144

@@ -164,7 +164,7 @@ async def test_control_method_error_message_for_different_methods():
164164

165165

166166
@pytest.mark.asyncio
167-
async def test_control_method_preserves_function_metadata():
167+
async def test_control_method_preserves_function_metadata() -> None:
168168
"""Test that control_method preserves the wrapped function's metadata."""
169169
equipment = MockEquipment(is_ready=True)
170170

@@ -177,7 +177,7 @@ async def test_control_method_preserves_function_metadata():
177177

178178

179179
@pytest.mark.asyncio
180-
async def test_control_method_without_omni_reference():
180+
async def test_control_method_without_omni_reference() -> None:
181181
"""Test that control_method logs warning when equipment lacks _omni reference."""
182182
equipment = MockEquipment(is_ready=True)
183183
del equipment._omni

0 commit comments

Comments
 (0)