Skip to content

Commit 389569c

Browse files
authored
Use explicit assertions following Python idioms (#2013)
Apply feedback from #1916
1 parent 8d6d16b commit 389569c

5 files changed

Lines changed: 82 additions & 71 deletions

File tree

tests/conftest.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""Shared pytest fixtures for the test suite."""
2+
3+
from __future__ import annotations
4+
5+
import pytest_asyncio
6+
7+
from pyoverkiz.auth.credentials import (
8+
LocalTokenCredentials,
9+
UsernamePasswordCredentials,
10+
)
11+
from pyoverkiz.client import OverkizClient
12+
from pyoverkiz.enums import Server
13+
from pyoverkiz.utils import create_local_server_config
14+
15+
16+
@pytest_asyncio.fixture
17+
async def client() -> OverkizClient:
18+
"""Fixture providing an OverkizClient configured for the cloud server."""
19+
return OverkizClient(
20+
server=Server.SOMFY_EUROPE,
21+
credentials=UsernamePasswordCredentials("username", "password"),
22+
)
23+
24+
25+
@pytest_asyncio.fixture
26+
async def local_client() -> OverkizClient:
27+
"""Fixture providing an OverkizClient configured for a local (developer) server."""
28+
return OverkizClient(
29+
server=create_local_server_config(host="gateway-1234-5678-1243.local:8443"),
30+
credentials=LocalTokenCredentials(token="token"), # noqa: S106
31+
)

tests/helpers.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""Shared test helpers for the test suite."""
2+
3+
from __future__ import annotations
4+
5+
import json
6+
from typing import Any, Self
7+
8+
9+
class MockResponse:
10+
"""Simple stand-in for aiohttp responses used in tests."""
11+
12+
def __init__(self, text: str, status: int = 200, url: str = "") -> None:
13+
"""Create a mock response with text payload and optional status/url."""
14+
self._text = text
15+
self.status = status
16+
self.url = url
17+
18+
async def text(self) -> str:
19+
"""Return text payload asynchronously."""
20+
return self._text
21+
22+
async def json(self, content_type: str | None = None) -> Any:
23+
"""Return parsed JSON payload asynchronously."""
24+
return json.loads(self._text)
25+
26+
async def __aexit__(self, exc_type, exc, tb) -> None:
27+
"""Context manager exit (noop)."""
28+
29+
async def __aenter__(self) -> Self:
30+
"""Context manager enter returning self."""
31+
return self

tests/test_client.py

Lines changed: 7 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
"""Unit tests for the high-level OverkizClient behaviour and responses."""
22

3-
# ruff: noqa: S106
4-
# S106: Test credentials use dummy values.
5-
63
from __future__ import annotations
74

85
import json
@@ -11,21 +8,15 @@
118

129
import aiohttp
1310
import pytest
14-
from pytest_asyncio import fixture
1511

1612
from pyoverkiz import exceptions
17-
from pyoverkiz.auth.credentials import (
18-
LocalTokenCredentials,
19-
UsernamePasswordCredentials,
20-
)
2113
from pyoverkiz.client import OverkizClient
2214
from pyoverkiz.enums import (
2315
APIType,
2416
DataType,
2517
ExecutionState,
2618
ExecutionSubType,
2719
ExecutionType,
28-
Server,
2920
)
3021
from pyoverkiz.models import (
3122
Action,
@@ -37,30 +28,14 @@
3728
State,
3829
)
3930
from pyoverkiz.response_handler import check_response
40-
from pyoverkiz.utils import create_local_server_config
31+
from tests.helpers import MockResponse
4132

4233
CURRENT_DIR = Path(__file__).resolve().parent
4334

4435

4536
class TestOverkizClient:
4637
"""Tests for the public OverkizClient behaviour (API type, devices, events, setup and diagnostics)."""
4738

48-
@fixture
49-
async def client(self):
50-
"""Fixture providing an OverkizClient configured for the cloud server."""
51-
return OverkizClient(
52-
server=Server.SOMFY_EUROPE,
53-
credentials=UsernamePasswordCredentials("username", "password"),
54-
)
55-
56-
@fixture
57-
async def local_client(self):
58-
"""Fixture providing an OverkizClient configured for a local (developer) server."""
59-
return OverkizClient(
60-
server=create_local_server_config(host="gateway-1234-5678-1243.local:8443"),
61-
credentials=LocalTokenCredentials(token="token"),
62-
)
63-
6439
@pytest.mark.asyncio
6540
async def test_get_api_type_cloud(self, client: OverkizClient):
6641
"""Verify that a cloud-configured client reports APIType.CLOUD."""
@@ -289,9 +264,9 @@ async def test_get_setup(
289264
assert setup.id is None
290265

291266
for device in setup.devices:
292-
assert device.identifier.gateway_id
293-
assert device.identifier.device_address
294-
assert device.identifier.protocol
267+
assert device.identifier.gateway_id is not None
268+
assert device.identifier.device_address is not None
269+
assert device.identifier.protocol is not None
295270

296271
@pytest.mark.parametrize(
297272
"fixture_name",
@@ -765,16 +740,16 @@ async def test_get_action_groups(
765740
assert len(action_groups) == scenario_count
766741

767742
for action_group in action_groups:
768-
assert action_group.oid
743+
assert action_group.oid is not None
769744
assert action_group.label is not None
770745
assert action_group.actions
771746

772747
for action in action_group.actions:
773-
assert action.device_url
748+
assert action.device_url is not None
774749
assert action.commands
775750

776751
for command in action.commands:
777-
assert command.name
752+
assert command.name is not None
778753

779754
@pytest.mark.asyncio
780755
async def test_get_current_execution_returns_execution(self, client: OverkizClient):
@@ -1141,29 +1116,3 @@ async def test_local_schedule_persisted_action_group_unknown_object(
11411116
await local_client.schedule_persisted_action_group(
11421117
"00000000-0000-0000-0000-000000000000", 9999999999
11431118
)
1144-
1145-
1146-
class MockResponse:
1147-
"""Simple stand-in for aiohttp responses used in tests."""
1148-
1149-
def __init__(self, text, status=200, url=""):
1150-
"""Create a mock response with text payload and optional status/url."""
1151-
self._text = text
1152-
self.status = status
1153-
self.url = url
1154-
1155-
async def text(self):
1156-
"""Return text payload asynchronously."""
1157-
return self._text
1158-
1159-
# pylint: disable=unused-argument
1160-
async def json(self, content_type=None):
1161-
"""Return parsed JSON payload asynchronously."""
1162-
return json.loads(self._text)
1163-
1164-
async def __aexit__(self, exc_type, exc, tb):
1165-
"""Context manager exit (noop)."""
1166-
1167-
async def __aenter__(self):
1168-
"""Context manager enter returning self."""
1169-
return self

tests/test_models.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ def test_none_states(self):
249249
raw = dict(RAW_DEVICES)
250250
del raw["states"]
251251
device = _make_device(raw)
252-
assert not device.states.get(STATE)
252+
assert device.states.get(STATE) is None
253253

254254
def test_select_first_command(self):
255255
"""Device.select_first_command() returns first supported command from list."""
@@ -379,19 +379,19 @@ def test_empty_states(self):
379379
"""An empty list yields an empty States object with no state found."""
380380
states = self._make_states([])
381381
assert not states
382-
assert not states.get(STATE)
382+
assert states.get(STATE) is None
383383

384384
def test_none_states(self):
385385
"""A None value for states should behave as empty."""
386386
states = self._make_states(None)
387387
assert not states
388-
assert not states.get(STATE)
388+
assert states.get(STATE) is None
389389

390390
def test_getter(self):
391391
"""Retrieve a known state and validate its properties."""
392392
states = self._make_states(RAW_STATES)
393393
state = states.get(STATE)
394-
assert state
394+
assert state is not None
395395
assert state.name == STATE
396396
assert state.type == DataType.STRING
397397
assert state.value == "alarm name"
@@ -400,7 +400,7 @@ def test_getter_missing(self):
400400
"""Requesting a missing state returns falsy (None)."""
401401
states = self._make_states(RAW_STATES)
402402
state = states.get("FooState")
403-
assert not state
403+
assert state is None
404404

405405
def test_select_returns_first_match(self):
406406
"""select() returns the first state with a non-None value."""
@@ -627,7 +627,7 @@ def test_bad_int_value(self):
627627
"""Accessor raises TypeError if the state type mismatches expected int."""
628628
state = State(name="state", type=DataType.BOOLEAN, value=False)
629629
with pytest.raises(TypeError):
630-
assert state.value_as_int
630+
_ = state.value_as_int
631631

632632
def test_float_value(self):
633633
"""Float typed state returns proper float accessor."""
@@ -638,7 +638,7 @@ def test_bad_float_value(self):
638638
"""Accessor raises TypeError if the state type mismatches expected float."""
639639
state = State(name="state", type=DataType.BOOLEAN, value=False)
640640
with pytest.raises(TypeError):
641-
assert state.value_as_float
641+
_ = state.value_as_float
642642

643643
def test_bool_value(self):
644644
"""Boolean typed state returns proper boolean accessor."""
@@ -649,7 +649,7 @@ def test_bad_bool_value(self):
649649
"""Accessor raises TypeError if the state type mismatches expected bool."""
650650
state = State(name="state", type=DataType.INTEGER, value=1)
651651
with pytest.raises(TypeError):
652-
assert state.value_as_bool
652+
_ = state.value_as_bool
653653

654654
def test_str_value(self):
655655
"""String typed state returns proper string accessor."""
@@ -660,7 +660,7 @@ def test_bad_str_value(self):
660660
"""Accessor raises TypeError if the state type mismatches expected string."""
661661
state = State(name="state", type=DataType.BOOLEAN, value=False)
662662
with pytest.raises(TypeError):
663-
assert state.value_as_str
663+
_ = state.value_as_str
664664

665665
def test_dict_value(self):
666666
"""JSON object typed state returns proper dict accessor."""
@@ -671,7 +671,7 @@ def test_bad_dict_value(self):
671671
"""Accessor raises TypeError if the state type mismatches expected dict."""
672672
state = State(name="state", type=DataType.BOOLEAN, value=False)
673673
with pytest.raises(TypeError):
674-
assert state.value_as_dict
674+
_ = state.value_as_dict
675675

676676
def test_list_value(self):
677677
"""JSON array typed state returns proper list accessor."""
@@ -682,7 +682,7 @@ def test_bad_list_value(self):
682682
"""Accessor raises TypeError if the state type mismatches expected list."""
683683
state = State(name="state", type=DataType.BOOLEAN, value=False)
684684
with pytest.raises(TypeError):
685-
assert state.value_as_list
685+
_ = state.value_as_list
686686

687687

688688
class TestEventState:

tests/test_utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def test_create_local_server_config(self):
1515
"""Create a local server descriptor using the host and default values."""
1616
local_server = create_local_server_config(host=LOCAL_HOST)
1717

18-
assert local_server
18+
assert local_server is not None
1919
assert (
2020
local_server.endpoint
2121
== "https://gateway-1234-5678-1243.local:8443/enduser-mobile-web/1/enduserAPI/"
@@ -33,7 +33,7 @@ def test_create_local_server_config_by_ip(self):
3333
configuration_url="https://somfy.com",
3434
)
3535

36-
assert local_server
36+
assert local_server is not None
3737
assert (
3838
local_server.endpoint
3939
== "https://192.168.1.105:8443/enduser-mobile-web/1/enduserAPI/"

0 commit comments

Comments
 (0)