Skip to content

Commit

Permalink
Drop IP/mDNS validation and add a (local) test
Browse files Browse the repository at this point in the history
The test does not mock anything out and depends on the correct IP
address / host name being specified via environmental variables.

Of note, I had experienced a peculiar issue with using a host name which
was mapped to multiple IP addresses in my /etc/hosts.
Curiously, this meant that the retrieval from the cache fetched the last
IP in the file, not, actually, the one used upon the original resolution.
This meant that AirQ.get worked perfectly when the DNS was resolved
de-novo, but timed out when it was cached.
While I am not sure if it is a bug or a feature of aiohttp, it is,
perhaps, irrelevant for this module and the entire test parametrisation,
which disables dns caching can be removed
  • Loading branch information
Sibgatulin committed Dec 23, 2023
1 parent b8a0d0a commit c3ea879
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 33 deletions.
6 changes: 3 additions & 3 deletions aioairq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
__email__ = "daniel.lehmann@corant.de"
__url__ = "https://www.air-q.com"
__license__ = "Apache License 2.0"
__version__ = "0.3.1"
__all__ = ["AirQ", "DeviceInfo", "InvalidAuth", "InvalidInput", "InvalidAirQResponse"]
__version__ = "0.3.2"
__all__ = ["AirQ", "DeviceInfo", "InvalidAuth", "InvalidAirQResponse"]

from aioairq.core import AirQ, DeviceInfo
from aioairq.exceptions import InvalidAirQResponse, InvalidAuth, InvalidInput
from aioairq.exceptions import InvalidAirQResponse, InvalidAuth
17 changes: 1 addition & 16 deletions aioairq/core.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import ipaddress
import json
import re
from typing import TypedDict

import aiohttp

from aioairq.encrypt import AESCipher
from aioairq.exceptions import InvalidAirQResponse, InvalidInput
from aioairq.exceptions import InvalidAirQResponse


class DeviceInfo(TypedDict):
Expand Down Expand Up @@ -51,25 +49,12 @@ def __init__(
on the same WiFi
"""

self.__class__._validate_address(address)
self.address = address
self.anchor = "http://" + self.address
self.aes = AESCipher(passw)
self._session = session
self._timeout = aiohttp.ClientTimeout(connect=timeout)

@classmethod
def _validate_address(cls, address: str) -> None:
"""Raise an error if address is not a valid IP or mDNS."""
if not re.match(r"^[a-f0-9]{5}_air-q\..+$", address):
try:
ipaddress.ip_address(address)
except ValueError:
raise InvalidInput(
f"{address} does not appear to be a valid IP address "
"or a 5-digit device ID"
)

async def validate(self) -> None:
"""Test if the password provided to the constructor is valid.
Expand Down
4 changes: 0 additions & 4 deletions aioairq/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,5 @@ class InvalidAuth(Exception):
"""Error to indicate an authentication failure."""


class InvalidInput(Exception):
"""Error to indicate the device ID / IP is invalid."""


class InvalidAirQResponse(Exception):
"""Error to indicate incorrect / unexpected response from the device"""
34 changes: 34 additions & 0 deletions tests/test_access.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import os

import aiohttp
import pytest
import pytest_asyncio

from aioairq import AirQ

SUBJECT = "ping"

PASS = os.environ.get("AIRQ_PASS", "placeholder_password")
IP = os.environ.get("AIRQ_IP", "192.168.0.0")
MDNS = os.environ.get("AIRQ_MDNS", "a123f_air-q.local")
HOSTNAME = os.environ.get("AIRQ_HOSTNAME", "air-q")


@pytest_asyncio.fixture()
async def session():
session = aiohttp.ClientSession()
yield session
await session.close()


@pytest.mark.asyncio
@pytest.mark.parametrize("address", [IP, HOSTNAME])
@pytest.mark.parametrize("repeat_call", [False, True])
async def test_dns_caching_by_repeated_calls(address, repeat_call, session):
"""Test if a repeated .get request results in a timeout
when DNS needs to be resolved / looked up from a cache.
"""
airq = AirQ(address, PASS, session, timeout=5)
await airq.get(SUBJECT)
if repeat_call:
await airq.get(SUBJECT)
11 changes: 1 addition & 10 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import pytest_asyncio
from pytest import fixture

from aioairq import AirQ, InvalidInput
from aioairq import AirQ


@fixture
Expand Down Expand Up @@ -33,15 +33,6 @@ def valid_address(request, ip, mdns):
return {"ip": ip, "mdns": mdns}[request.param]


def test_address_parser(valid_address):
AirQ._validate_address(valid_address)


def test_address_parser_failure():
with pytest.raises(InvalidInput):
AirQ._validate_address("illegal_address")


@pytest.mark.asyncio
async def test_constructor(valid_address, passw, session):
airq = AirQ(valid_address, passw, session)
Expand Down

0 comments on commit c3ea879

Please sign in to comment.