Skip to content

Commit d5a73d4

Browse files
committed
add missing testing.
Some updates to dependencies and conftest were also required. Updated ruff version did formatting slightly different in a few files.
1 parent 171509f commit d5a73d4

14 files changed

Lines changed: 1411 additions & 1002 deletions

.pre-commit-config.yaml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ repos:
1010
hooks:
1111
- id: trailing-whitespace
1212
rev: "v4.1.0"
13-
- repo: https://github.com/charliermarsh/ruff-pre-commit
14-
rev: 'v0.0.209'
13+
- repo: https://github.com/astral-sh/ruff-pre-commit
14+
rev: 'v0.15.6'
1515
hooks:
16-
- id: ruff
16+
- id: ruff-format
17+
- id: ruff-check
18+
- id: ruff-format

homeassistant_api/models/domains.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -383,9 +383,9 @@ class ServiceFieldSelectorObject(BaseModel):
383383
class ServiceFieldSelectorQRCode(BaseModel):
384384
data: str
385385
scale: Optional[Union[int, float]] = None
386-
error_correction_level: Optional[
387-
ServiceFieldSelectorQRCodeErrorCorrectionLevel
388-
] = None
386+
error_correction_level: Optional[ServiceFieldSelectorQRCodeErrorCorrectionLevel] = (
387+
None
388+
)
389389
center_image: Optional[str] = None
390390

391391

homeassistant_api/rawasyncclient.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ async def async_request(
112112
)
113113
except asyncio.exceptions.TimeoutError as err:
114114
raise RequestTimeoutError(
115-
f'Home Assistant did not respond in time (timeout: {kwargs.get("timeout", 300)} sec)',
115+
f"Home Assistant did not respond in time (timeout: {kwargs.get('timeout', 300)} sec)",
116116
self.endpoint(path) + f"?{params}" * bool(params),
117117
) from err
118118

homeassistant_api/rawclient.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def request(
113113
)
114114
except requests.exceptions.Timeout as err:
115115
raise RequestTimeoutError(
116-
f'Home Assistant did not respond in time (timeout: {kwargs.get("timeout", 300)} sec)',
116+
f"Home Assistant did not respond in time (timeout: {kwargs.get('timeout', 300)} sec)",
117117
url=self.endpoint(path) + f"?{params}" * bool(params),
118118
) from err
119119
return self.response_logic(response=resp, decode_bytes=decode_bytes)

homeassistant_api/rawwebsocket.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,12 @@ def __init__(
4141
self._conn = None
4242

4343
self._id_counter = 0
44-
self._result_responses: dict[int, Optional[ResultResponse]] = (
45-
{}
46-
) # id -> response
47-
self._event_responses: dict[int, list[EventResponse]] = (
48-
{}
49-
) # id -> [response, ...]
44+
self._result_responses: dict[
45+
int, Optional[ResultResponse]
46+
] = {} # id -> response
47+
self._event_responses: dict[
48+
int, list[EventResponse]
49+
] = {} # id -> [response, ...]
5050
self._ping_responses: dict[int, PingResponse] = {} # id -> (sent, received)
5151

5252
def __repr__(self) -> str:

homeassistant_api/utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66

77
if TYPE_CHECKING or os.getenv("DOCUMENTATION_MODE") != "true":
88
JSONType = TypeAliasType(
9-
"JSONType", "Optional[Union[int, float, str, bool, list[JSONType], dict[str, JSONType]]]"
9+
"JSONType",
10+
"Optional[Union[int, float, str, bool, list[JSONType], dict[str, JSONType]]]",
1011
)
1112
else:
1213
JSONType = type("JSONType", (object,), {})

poetry.lock

Lines changed: 1167 additions & 967 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,19 +43,20 @@ types-requests = "^2.27.9"
4343
types-simplejson = "^3.17.3"
4444
types-toml = "^0.10.4"
4545
mypy = "^1.8.0"
46-
ruff = "^0.0.209"
46+
ruff = "^0.15"
4747

4848
[tool.poetry.group.testing]
4949
optional = true
50-
5150
[tool.poetry.group.testing.dependencies]
52-
pytest-asyncio = "^0.18.3"
53-
pytest-cov = "^3.0.0"
54-
pytest = "^6.2.5"
55-
aiosqlite = "^0.20.0"
51+
pytest-asyncio = "^1"
52+
pytest-cov = "^7"
53+
pytest = "^8"
54+
aiosqlite = "^0.22"
5655

5756
[tool.pytest.ini_options]
5857
asyncio_mode = "auto"
58+
asyncio_default_fixture_loop_scope = "session"
59+
asyncio_default_test_loop_scope = "session"
5960
log_cli = true
6061
# log_cli_level = "DEBUG"
6162
log_cli_format = "%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)"
@@ -72,7 +73,7 @@ exclude_lines = [
7273
"pragma: no cover"
7374
]
7475

75-
[tool.ruff]
76+
[tool.ruff.lint]
7677
exclude = [
7778
'.git',
7879
'__pycache__',
@@ -85,7 +86,7 @@ select = [
8586
'W',
8687
]
8788

88-
[tool.ruff.per-file-ignores]
89+
[tool.ruff.lint.per-file-ignores]
8990
"__init__.py" = ['F401']
9091
"conf.py" = ['E402']
9192

tests/conftest.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import asyncio
21
import logging
32
import os
43
from typing import AsyncGenerator, Generator, Literal, cast
@@ -35,14 +34,6 @@ def setup_cached_client(wait_for_server) -> Generator[Client, None, None]:
3534
yield cast(Client, client)
3635

3736

38-
@pytest.fixture(scope="session")
39-
def event_loop():
40-
"""Redefines the event loop with a broader scope."""
41-
loop = asyncio.new_event_loop()
42-
yield loop
43-
loop.close()
44-
45-
4637
@pytest_asyncio.fixture(name="async_cached_client", scope="session")
4738
async def setup_async_cached_client(
4839
wait_for_server: Literal[None],

tests/test_endpoints.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import logging
44
from datetime import datetime
55

6+
import pytest
7+
68
from homeassistant_api import Client
79
from homeassistant_api.errors import RequestError
810
from homeassistant_api.models import ConfigEntryDisabler
@@ -154,6 +156,48 @@ async def test_async_get_entities(async_cached_client: Client) -> None:
154156
assert "sun" in entities
155157

156158

159+
def test_websocket_get_config(websocket_client: WebsocketClient) -> None:
160+
"""Tests the `"type": "get_config"` websocket command."""
161+
config = websocket_client.get_config()
162+
assert isinstance(config, dict)
163+
assert config.get("state") in {"RUNNING", "NOT_RUNNING"}
164+
165+
166+
def test_websocket_get_state(websocket_client: WebsocketClient) -> None:
167+
"""Tests WebsocketClient.get_state with entity_id."""
168+
state = websocket_client.get_state(entity_id="sun.sun")
169+
assert state.entity_id == "sun.sun"
170+
assert state.state in {"above_horizon", "below_horizon"}
171+
172+
173+
def test_websocket_get_entity_by_group_slug(websocket_client: WebsocketClient) -> None:
174+
"""Tests WebsocketClient.get_entity with group_id and slug."""
175+
entity = websocket_client.get_entity(group_id="sun", slug="sun")
176+
assert entity is not None
177+
assert entity.entity_id == "sun.sun"
178+
179+
180+
def test_websocket_get_entity_by_entity_id(websocket_client: WebsocketClient) -> None:
181+
"""Tests WebsocketClient.get_entity with entity_id."""
182+
entity = websocket_client.get_entity(entity_id="sun.sun")
183+
assert entity is not None
184+
assert entity.entity_id == "sun.sun"
185+
186+
187+
def test_websocket_get_entity_no_args(websocket_client: WebsocketClient) -> None:
188+
"""Tests WebsocketClient.get_entity raises ValueError with no arguments."""
189+
with pytest.raises(
190+
ValueError, match="Neither group_id and slug or entity_id provided"
191+
):
192+
websocket_client.get_entity()
193+
194+
195+
def test_websocket_get_state_not_found(websocket_client: WebsocketClient) -> None:
196+
"""Tests WebsocketClient.get_state raises ValueError for nonexistent entity."""
197+
with pytest.raises(ValueError, match="not found"):
198+
websocket_client.get_state(entity_id="fake.nonexistent_entity_12345")
199+
200+
157201
def test_websocket_get_entities(websocket_client: WebsocketClient) -> None:
158202
"""Tests the `"type": "get_entities"` websocket command."""
159203
entities = websocket_client.get_entities()
@@ -325,6 +369,22 @@ def test_websocket_trigger_service(websocket_client: WebsocketClient) -> None:
325369
assert resp is None
326370

327371

372+
def test_websocket_trigger_service_with_entity_id(
373+
websocket_client: WebsocketClient,
374+
) -> None:
375+
"""Tests websocket trigger_service with an entity_id target."""
376+
state_before = websocket_client.get_state(entity_id="sun.sun")
377+
websocket_client.trigger_service(
378+
"homeassistant",
379+
"update_entity",
380+
entity_id="sun.sun",
381+
)
382+
state_after = websocket_client.get_state(entity_id="sun.sun")
383+
# update_entity refreshes the entity; state should remain valid
384+
assert state_after.entity_id == state_before.entity_id
385+
assert state_after.state in {"above_horizon", "below_horizon"}
386+
387+
328388
def test_trigger_service_with_response(cached_client: Client) -> None:
329389
"""Tests the `POST /api/services/<domain>/<service>?return_response` endpoint."""
330390
weather = cached_client.get_domain("weather")

0 commit comments

Comments
 (0)