Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions aiopnsense/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
OPNsenseBelowMinFirmware,
OPNsenseConnectionError,
OPNsenseError,
OPNsenseInvalidAuth,
OPNsenseInvalidArgument,
OPNsenseInvalidAuth,
OPNsenseInvalidURL,
OPNsenseMissingDeviceUniqueID,
OPNsensePrivilegeMissing,
Expand All @@ -21,8 +21,8 @@
"OPNsenseClient",
"OPNsenseConnectionError",
"OPNsenseError",
"OPNsenseInvalidAuth",
"OPNsenseInvalidArgument",
"OPNsenseInvalidAuth",
"OPNsenseInvalidURL",
"OPNsenseMissingDeviceUniqueID",
"OPNsensePrivilegeMissing",
Expand Down
6 changes: 1 addition & 5 deletions aiopnsense/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,7 @@
from .client_base import ClientBaseMixin
from .const import OPNSENSE_LTD_FIRMWARE, OPNSENSE_MIN_FIRMWARE
from .dhcp import DHCPMixin
from .exceptions import (
OPNsenseBelowMinFirmware,
OPNsenseUnknownFirmware,
_map_opnsense_exception,
)
from .exceptions import OPNsenseBelowMinFirmware, OPNsenseUnknownFirmware, _map_opnsense_exception
from .firewall import FirewallMixin
from .firmware import FirmwareMixin
from .helpers import _LOGGER, firmware_is_at_least
Expand Down
3 changes: 1 addition & 2 deletions aiopnsense/client_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,7 @@ def _drain_buffer(
if not line.startswith("data:"):
continue
value = line[len("data:") :]
if value.startswith(" "):
value = value[1:]
value = value.removeprefix(" ")
data_lines.append(value)

if not data_lines:
Expand Down
2 changes: 1 addition & 1 deletion aiopnsense/nut.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""NUT plugin methods for OPNsenseClient."""

import re
from collections.abc import Mapping
import re
from typing import Any

from ._typing import AiopnsenseClientProtocol
Expand Down
7 changes: 2 additions & 5 deletions aiopnsense/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,8 @@
from dateutil.parser import ParserError, UnknownTimezoneWarning, parse

from ._typing import AiopnsenseClientProtocol
from .const import (
AMBIGUOUS_TZINFOS,
OPNSENSE_26_1_11_COMPAT_FIRMWARE,
)
from .exceptions import OPNsenseMissingDeviceUniqueID, OPNsenseError
from .const import AMBIGUOUS_TZINFOS, OPNSENSE_26_1_11_COMPAT_FIRMWARE
from .exceptions import OPNsenseError, OPNsenseMissingDeviceUniqueID
from .helpers import (
_LOGGER,
_log_errors,
Expand Down
2 changes: 1 addition & 1 deletion aiopnsense/traffic.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
from collections.abc import AsyncIterator, Mapping
from typing import Any

from ._typing import AiopnsenseClientProtocol
from .client_transport import _STREAM_JSON_EVENT_RESET_KEY
from .const import DEFAULT_REQUEST_TIMEOUT_SECONDS
from .exceptions import OPNsenseError, _map_opnsense_exception
from ._typing import AiopnsenseClientProtocol
from .helpers import _LOGGER, try_to_float, try_to_int

DIAGNOSTICS_TRAFFIC_ENDPOINT = "/api/diagnostics/traffic/interface"
Expand Down
11 changes: 6 additions & 5 deletions scripts/_opnsense_live_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@

from __future__ import annotations

import json
import os
import re
from collections.abc import Mapping
from dataclasses import dataclass
from functools import cache
import json
import os
from pathlib import Path
import re
import sys
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
import aiohttp

from aiopnsense import OPNsenseClient


Expand Down Expand Up @@ -255,7 +256,7 @@ def write_output(payload: Any, output_path: Path | None = None) -> None:


@cache
def _get_client_class() -> type["OPNsenseClient"]:
def _get_client_class() -> type[OPNsenseClient]:
"""Return the lazily imported OPNsense client class.

Returns:
Expand All @@ -266,7 +267,7 @@ def _get_client_class() -> type["OPNsenseClient"]:
return OPNsenseClient


def create_client(config: LiveConfig, session: aiohttp.ClientSession) -> "OPNsenseClient":
def create_client(config: LiveConfig, session: aiohttp.ClientSession) -> OPNsenseClient:
"""Create an OPNsense client for live scripts.

Args:
Expand Down
9 changes: 5 additions & 4 deletions scripts/aiopnsense_dump.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

import argparse
import asyncio
from dataclasses import dataclass
import importlib
import logging
import time
from dataclasses import dataclass
from pathlib import Path
import time
from typing import Any

_common = importlib.import_module("_opnsense_live_common")
Expand All @@ -25,8 +25,9 @@
if __name__ == "__main__":
reexec_with_repo_venv(Path(__file__))

import aiohttp # noqa: E402
from aiopnsense.exceptions import OPNsenseError # noqa: E402
import aiohttp

from aiopnsense.exceptions import OPNsenseError
Comment thread
greptile-apps[bot] marked this conversation as resolved.

_LOGGER = logging.getLogger(__name__)

Expand Down
4 changes: 2 additions & 2 deletions scripts/opnsense_api_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
if __name__ == "__main__":
reexec_with_repo_venv(Path(__file__))

import aiohttp # noqa: E402
import aiohttp

_NO_PAYLOAD = object()

Expand Down Expand Up @@ -164,7 +164,7 @@ def load_payload(

async def call_api(
session: aiohttp.ClientSession,
config: "LiveConfig",
config: LiveConfig,
endpoint: str,
method: str,
payload: dict[str, Any] | object,
Expand Down
6 changes: 3 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def legacy_dnsbl_payload() -> dict[str, Any]:
class FakeClientSession:
"""Minimal fake aiohttp session for unit tests."""

async def __aenter__(self) -> "FakeClientSession":
async def __aenter__(self) -> FakeClientSession:
"""Enter async context.

Returns:
Expand Down Expand Up @@ -276,7 +276,7 @@ def done(self) -> bool:
"""
return False

def __await__(self) -> Generator[None, None, None]:
def __await__(self) -> Generator[None]:
"""Await.

Returns:
Expand Down Expand Up @@ -394,7 +394,7 @@ def _make(


@pytest.fixture
async def make_client() -> AsyncGenerator[Callable[..., aiopnsense.OPNsenseClient], None]:
async def make_client() -> AsyncGenerator[Callable[..., aiopnsense.OPNsenseClient]]:
"""Return a factory that constructs an OPNsenseClient for tests.

Yields:
Expand Down
53 changes: 28 additions & 25 deletions tests/test_client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,8 @@
import aiohttp
import pytest

from aiopnsense import (
OPNsenseClient,
client as aiopnsense_client,
)
from aiopnsense.const import (
OPNSENSE_LTD_FIRMWARE,
OPNSENSE_MIN_FIRMWARE,
)
from aiopnsense import OPNsenseClient, client as aiopnsense_client
from aiopnsense.const import OPNSENSE_LTD_FIRMWARE, OPNSENSE_MIN_FIRMWARE
from aiopnsense.exceptions import (
OPNsenseBelowMinFirmware,
OPNsenseConnectionError,
Expand Down Expand Up @@ -457,29 +451,38 @@ async def test_client_constructor_throw_errors_configuration(


@pytest.mark.asyncio
async def test_client_constructor_invalid_throw_errors_raises_type_error(
@pytest.mark.parametrize(
("kwargs", "error_message"),
[
pytest.param(
{"throw_errors": "false"},
"`throw_errors` must be a bool",
id="throw-errors",
),
pytest.param(
{"initial": "false"},
"`initial` must be a bool",
id="legacy-initial",
),
],
)
async def test_client_constructor_invalid_boolean_option_raises_type_error(
kwargs: dict[str, str],
error_message: str,
make_client: MakeClientFactory,
) -> None:
"""Verify invalid ``throw_errors`` values raise ``TypeError``.
"""Verify invalid constructor boolean options raise ``TypeError``.

Args:
make_client (MakeClientFactory): Fixture factory used to pass an invalid constructor option.
"""
with pytest.raises(TypeError, match="`throw_errors` must be a bool"):
make_client(throw_errors="false")
kwargs (dict[str, str]): Invalid constructor option and value.
error_message (str): Exact validation error expected for the option.
make_client (MakeClientFactory): Fixture factory used to exercise option validation.


@pytest.mark.asyncio
async def test_client_constructor_invalid_initial_raises_type_error(
make_client: MakeClientFactory,
) -> None:
"""Verify invalid deprecated ``initial`` values raise ``TypeError``.

Args:
make_client (MakeClientFactory): Fixture factory used to exercise legacy-option validation.
Returns:
None: This test validates constructor option type enforcement.
"""
with pytest.raises(TypeError, match="`initial` must be a bool"):
make_client(initial="false")
with pytest.raises(TypeError, match=error_message):
make_client(**kwargs)


@pytest.mark.asyncio
Expand Down
41 changes: 16 additions & 25 deletions tests/test_client_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -963,49 +963,40 @@ async def test_set_use_snake_case_selects_expected_endpoint_style(
await client.async_close()


@pytest.mark.parametrize(
("firmware_version", "expected_use_snake_case"),
[
pytest.param("invalid-version", True, id="invalid-version"),
pytest.param(None, None, id="missing-version"),
],
)
@pytest.mark.asyncio
async def test_set_use_snake_case_initial_raises_unknown_firmware(
make_client: MakeClientFactory,
firmware_version: str | None,
expected_use_snake_case: bool | None,
) -> None:
"""Verify legacy initial setup still raises for invalid firmware versions.
"""Verify legacy initial setup raises when firmware cannot be identified.

Args:
make_client (MakeClientFactory): Fixture factory returning ``OPNsenseClient`` instances.
firmware_version (str | None): Invalid or missing firmware version returned by the client.
expected_use_snake_case (bool | None): Expected state to assert, when covered by the
original case.

Returns:
None: This test asserts the preserved compatibility path.
"""
client = make_client()
try:
client.get_host_firmware_version = AsyncMock(return_value="invalid-version")

with pytest.raises(OPNsenseUnknownFirmware):
await client._set_use_snake_case(initial=True)

client.get_host_firmware_version.assert_awaited_once_with()
assert client._use_snake_case is True
finally:
await client.async_close()


@pytest.mark.asyncio
async def test_set_use_snake_case_initial_raises_unknown_firmware_when_version_missing(
make_client: MakeClientFactory,
) -> None:
"""Verify missing firmware version still raises unknown-firmware in legacy init mode.

Args:
make_client (MakeClientFactory): Fixture factory used to simulate a client without firmware data.
"""

client = make_client()
try:
client.get_host_firmware_version = AsyncMock(return_value=None)
client.get_host_firmware_version = AsyncMock(return_value=firmware_version)

with pytest.raises(OPNsenseUnknownFirmware):
await client._set_use_snake_case(initial=True)

client.get_host_firmware_version.assert_awaited_once_with()
if expected_use_snake_case is not None:
assert client._use_snake_case is expected_use_snake_case
Comment thread
coderabbitai[bot] marked this conversation as resolved.
finally:
await client.async_close()

Expand Down
Loading
Loading