From 695e3c0f4df1e905a694050b0c703e29a72b8fc7 Mon Sep 17 00:00:00 2001 From: Tom Scholten Date: Tue, 29 Jul 2025 00:08:21 +0200 Subject: [PATCH 1/2] Discovery fixes --- airos/discovery.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/airos/discovery.py b/airos/discovery.py index 5f85a05..d070136 100644 --- a/airos/discovery.py +++ b/airos/discovery.py @@ -59,7 +59,7 @@ def datagram_received(self, data: bytes, addr: tuple[str, int]) -> None: parsed_data: dict[str, Any] | None = self.parse_airos_packet(data, host_ip) if parsed_data: # Schedule the user-provided callback, don't await to keep listener responsive - asyncio.create_task(self.callback(parsed_data)) # noqa: RUF006 + self.callback(parsed_data) except (AirosEndpointError, AirosListenerError) as err: # These are expected types of malformed packets. Log the specific error # and then re-raise as AirosDiscoveryError. diff --git a/pyproject.toml b/pyproject.toml index 66800dc..d597ad1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "airos" -version = "0.2.0" +version = "0.2.1" license = "MIT" description = "Ubiquity airOS module(s) for Python 3." readme = "README.md" From 4f46b6a356244f7b6680ec50a7adf406579ed47e Mon Sep 17 00:00:00 2001 From: Tom Scholten Date: Tue, 29 Jul 2025 00:14:19 +0200 Subject: [PATCH 2/2] Fix async --- pyproject.toml | 2 +- tests/test_discovery.py | 62 +++++++++++++++++++++++++++++++++-------- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d597ad1..8f67e32 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "airos" -version = "0.2.1" +version = "0.2.1a1" license = "MIT" description = "Ubiquity airOS module(s) for Python 3." readme = "README.md" diff --git a/tests/test_discovery.py b/tests/test_discovery.py index 5d22e46..d9151cd 100644 --- a/tests/test_discovery.py +++ b/tests/test_discovery.py @@ -9,6 +9,17 @@ from airos.exceptions import AirosDiscoveryError, AirosEndpointError import pytest +expected_parsed_data_for_mock_packet = { + "ip_address": "192.168.1.3", # This comes from the host_ip passed to datagram_received + "mac_address": "01:23:45:67:89:CD", # From 0x06 TLV + "hostname": "name", # From 0x0B TLV + "model": "NanoStation 5AC loco", # From 0x0C TLV + "firmware_version": "WA.V8.7.17", # From 0x03 TLV + "uptime_seconds": 3231, # From 0x0A TLV (0x0C9F in hex) + "ssid": "DemoSSID", # From 0x0D TLV + "full_model_name": "NanoStation 5AC loco", # From 0x14 TLV +} + # Helper to load binary fixture async def _read_binary_fixture(fixture_name: str) -> bytes: @@ -119,21 +130,48 @@ async def test_datagram_received_calls_callback(mock_airos_packet): protocol = AirosDiscoveryProtocol(mock_callback) host_ip = "192.168.1.3" # Sender IP - with patch("asyncio.create_task") as mock_create_task: - protocol.datagram_received(mock_airos_packet, (host_ip, DISCOVERY_PORT)) + protocol.datagram_received(mock_airos_packet, (host_ip, DISCOVERY_PORT)) - # Verify the task was created and get the coroutine - mock_create_task.assert_called_once() - task_coro = mock_create_task.call_args[0][0] + # Assert that the mock_callback was called exactly once + mock_callback.assert_called_once() - # Manually await the coroutine to test the callback - await task_coro + # Get the arguments it was called with + args, kwargs = mock_callback.call_args - mock_callback.assert_called_once() - called_args, _ = mock_callback.call_args - parsed_data = called_args[0] - assert parsed_data["ip_address"] == "192.168.1.3" - assert parsed_data["mac_address"] == "01:23:45:67:89:CD" # Verify scrubbed MAC + # Assert that the callback was called with a single argument (the parsed data) + assert len(args) == 1 + assert not kwargs # No keyword arguments expected + + # Get the parsed data from the call + actual_parsed_data = args[0] + + # Assert that the parsed data matches the expected structure and values + # We need to make sure the IP address in the expected data reflects the host_ip + expected_parsed_data_with_current_ip = expected_parsed_data_for_mock_packet.copy() + expected_parsed_data_with_current_ip["ip_address"] = host_ip + + # For robust testing, you might want to only check the critical fields, + # or ensure your `parse_airos_packet` is separately tested to be correct. + # Here, we'll assert a subset of key fields for simplicity. + assert ( + actual_parsed_data.get("mac_address") + == expected_parsed_data_with_current_ip["mac_address"] + ) + assert ( + actual_parsed_data.get("hostname") + == expected_parsed_data_with_current_ip["hostname"] + ) + assert ( + actual_parsed_data.get("ip_address") + == expected_parsed_data_with_current_ip["ip_address"] + ) + assert ( + actual_parsed_data.get("model") == expected_parsed_data_with_current_ip["model"] + ) + assert ( + actual_parsed_data.get("firmware_version") + == expected_parsed_data_with_current_ip["firmware_version"] + ) @pytest.mark.asyncio