Skip to content

Commit 36810ab

Browse files
rsnodgrassSage-Ox
andcommitted
Add comprehensive test suite for pyavcontrol
- Add test_connection.py, test_device_client.py, test_device_library.py - Add test_device_model.py, test_utils.py - Update conftest.py with fixtures Co-Authored-By: SageOx <ox@sageox.ai>
1 parent 36a5789 commit 36810ab

9 files changed

Lines changed: 867 additions & 212 deletions

tests/conftest.py

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,21 @@
11
"""
2-
Pytest configuration and fixtures for testing pyavcontrol without physical devices.
2+
Pytest configuration and fixtures for testing pyavcontrol.
33
44
All tests use mocked serial/IP connections to avoid needing real hardware.
55
"""
66

7+
from __future__ import annotations
8+
79
import asyncio
810
from pathlib import Path
11+
from typing import Any
912
from unittest.mock import AsyncMock, Mock
1013

1114
import pytest
1215

1316

1417
@pytest.fixture
15-
def event_loop():
16-
"""Create an event loop for async tests."""
17-
loop = asyncio.new_event_loop()
18-
yield loop
19-
loop.close()
20-
21-
22-
@pytest.fixture
23-
def mock_serial_transport():
18+
def mock_serial_transport() -> Mock:
2419
"""Mock pyserial transport for RS232 connections."""
2520
transport = Mock()
2621
transport.serial = Mock()
@@ -31,23 +26,20 @@ def mock_serial_transport():
3126

3227

3328
@pytest.fixture
34-
def mock_serial_protocol(mock_serial_transport):
29+
def mock_serial_protocol(mock_serial_transport: Mock) -> AsyncMock:
3530
"""Mock RS232 protocol for testing device communication."""
3631
protocol = AsyncMock()
3732
protocol._transport = mock_serial_transport
3833
protocol._connected = asyncio.Event()
39-
protocol._connected.set() # Start as connected
34+
protocol._connected.set()
4035
protocol._q = asyncio.Queue()
41-
42-
# Mock send to return predefined responses
4336
protocol.send = AsyncMock(return_value=b'OK\r')
4437
protocol.receive_response = AsyncMock(return_value=b'OK\r')
45-
4638
return protocol
4739

4840

4941
@pytest.fixture
50-
def mock_connection(mock_serial_protocol):
42+
def mock_connection(mock_serial_protocol: AsyncMock) -> AsyncMock:
5143
"""Mock AsyncDeviceConnection for testing clients."""
5244
connection = AsyncMock()
5345
connection._legacy_connection = mock_serial_protocol
@@ -58,7 +50,7 @@ def mock_connection(mock_serial_protocol):
5850

5951

6052
@pytest.fixture
61-
def sample_device_definition():
53+
def sample_device_definition() -> dict[str, Any]:
6254
"""Sample device definition for testing."""
6355
return {
6456
'id': 'test_device',
@@ -126,12 +118,12 @@ def sample_device_definition():
126118

127119

128120
@pytest.fixture
129-
def yaml_library_path():
121+
def yaml_library_path() -> Path:
130122
"""Path to YAML device definitions."""
131123
return Path(__file__).parent.parent / 'pyavcontrol' / 'data' / 'src'
132124

133125

134126
@pytest.fixture
135-
def all_device_yaml_files(yaml_library_path):
127+
def all_device_yaml_files(yaml_library_path: Path) -> list[Path]:
136128
"""List all device YAML files for integration testing."""
137129
return list(yaml_library_path.glob('*.yaml'))

tests/init.py

Whitespace-only changes.

tests/test_connection.py

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,97 @@
1-
"""
2-
Test behavior of the connection implementions
3-
"""
1+
"""Tests for connection implementations."""
2+
3+
from __future__ import annotations
4+
5+
from unittest.mock import Mock, patch
6+
7+
import pytest
8+
9+
from pyavcontrol.connection import Connection, DeviceConnection, NullConnection
10+
11+
12+
class TestNullConnection:
13+
"""Test NullConnection implementation."""
14+
15+
def test_is_connected_always_true(self) -> None:
16+
"""NullConnection is always connected."""
17+
conn = NullConnection()
18+
assert conn.is_connected() is True
19+
20+
def test_send_returns_none(self) -> None:
21+
"""NullConnection send returns None."""
22+
conn = NullConnection()
23+
result = conn.send(b'test data')
24+
assert result is None
25+
26+
def test_send_with_callback(self) -> None:
27+
"""NullConnection ignores callback."""
28+
conn = NullConnection()
29+
callback_called = False
30+
31+
def callback(data: bytes) -> None:
32+
nonlocal callback_called
33+
callback_called = True
34+
35+
conn.send(b'test', callback=callback)
36+
assert not callback_called
37+
38+
def test_is_async_returns_false(self) -> None:
39+
"""NullConnection is synchronous."""
40+
conn = NullConnection()
41+
assert conn.is_async() is False
42+
43+
44+
class TestConnectionFactory:
45+
"""Test Connection factory method."""
46+
47+
def test_create_sync_connection(self) -> None:
48+
"""Test creating synchronous connection."""
49+
with patch(
50+
'pyavcontrol.connection.sync_connection.serial.serial_for_url'
51+
) as mock_serial:
52+
mock_port = Mock()
53+
mock_serial.return_value = mock_port
54+
55+
conn = Connection.create('/dev/ttyUSB0', {'baudrate': 9600})
56+
57+
assert conn is not None
58+
assert not conn.is_async()
59+
60+
def test_create_async_connection(self) -> None:
61+
"""Test creating asynchronous connection with event loop."""
62+
import asyncio
63+
64+
loop = asyncio.new_event_loop()
65+
try:
66+
with patch(
67+
'pyavcontrol.connection.async_connection.asyncio.create_task'
68+
):
69+
conn = Connection.create(
70+
'/dev/ttyUSB0', {'baudrate': 9600}, event_loop=loop
71+
)
72+
73+
assert conn is not None
74+
assert conn.is_async()
75+
finally:
76+
loop.close()
77+
78+
def test_create_with_empty_config(self) -> None:
79+
"""Test creating connection with no config."""
80+
with patch(
81+
'pyavcontrol.connection.sync_connection.serial.serial_for_url'
82+
) as mock_serial:
83+
mock_port = Mock()
84+
mock_serial.return_value = mock_port
85+
86+
conn = Connection.create('/dev/ttyUSB0')
87+
88+
assert conn is not None
89+
90+
91+
class TestDeviceConnectionInterface:
92+
"""Test DeviceConnection abstract interface."""
93+
94+
def test_repr_returns_class_name(self) -> None:
95+
"""Test default repr implementation."""
96+
conn = NullConnection()
97+
assert 'NullConnection' in repr(conn)

0 commit comments

Comments
 (0)