Skip to content

Commit cadf6a4

Browse files
committed
test: Hold the auth state gates the fix left unpinned
Reverting two of the five gates to a truthiness check left the suite green, so the bug they fix could come back unnoticed. The status read path was covered only by a test whose cache is a MagicMock, which is truthy whatever it holds and so cannot see the difference, and the RPC client's clear on a failed authentication had no test at all. Both now use a real empty cache, which is the state the bug needs. Two tests in the gateway file said less than their names. The custom timeout one passed no timeout and asserted the default, making it a copy of the test above it; it now passes one and follows it to both requests. The auth error one fed two answers to four gathered reads, so the last two died as StopIteration and were swallowed as ordinary failures; it now answers all four.
1 parent 0101c4a commit cadf6a4

2 files changed

Lines changed: 56 additions & 3 deletions

File tree

packages/core/tests/unit/gateways/device/test_shelly_device_gateway.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,18 +76,18 @@ async def test_it_discovers_device_with_custom_timeout(
7676
side_effect=[(device_info, 0.2), (update_info, 0.05)]
7777
)
7878

79-
result = await gateway.discover_device("192.168.1.100")
79+
result = await gateway.discover_device("192.168.1.100", timeout=2.5)
8080

8181
assert result is not None
8282
assert mock_rpc_client.make_rpc_request.call_count == 2
8383
calls = mock_rpc_client.make_rpc_request.call_args_list
8484
assert calls[0] == (
8585
("192.168.1.100", "Shelly.GetDeviceInfo"),
86-
{"timeout": 10.0},
86+
{"timeout": 2.5},
8787
)
8888
assert calls[1] == (
8989
("192.168.1.100", "Shelly.CheckForUpdate"),
90-
{"timeout": 10.0},
90+
{"timeout": 2.5},
9191
)
9292

9393
async def test_it_handles_device_discovery_failure(
@@ -374,6 +374,8 @@ async def test_it_propagates_auth_error_from_get_device_status(
374374
side_effect=[
375375
({"name": "Test", "model": "SAWD-0A1XX10EU1"}, 0.05),
376376
DeviceAuthenticationError("192.168.1.100", "No credentials stored"),
377+
({"sys": {}}, 0.1),
378+
({"methods": []}, 0.05),
377379
]
378380
)
379381

@@ -401,6 +403,28 @@ async def test_it_marks_auth_required_while_the_cache_is_still_empty(
401403
assert result.auth_required is True
402404
assert auth_state_cache.requires_auth("192.168.1.100") is True
403405

406+
async def test_it_marks_auth_required_from_a_status_read_on_an_empty_cache(
407+
self, mock_rpc_client, mock_legacy_gateway
408+
):
409+
auth_state_cache = AuthStateCache()
410+
gateway = ShellyDeviceGateway(
411+
rpc_client=mock_rpc_client,
412+
legacy_gateway=mock_legacy_gateway,
413+
auth_state_cache=auth_state_cache,
414+
)
415+
mock_rpc_client.make_rpc_request = AsyncMock(
416+
side_effect=[
417+
({"auth_en": True}, 0.05),
418+
({"components": [], "cfg_rev": 1, "total": 0}, 0.1),
419+
({"sys": {}}, 0.1),
420+
({"methods": []}, 0.05),
421+
]
422+
)
423+
424+
await gateway.get_device_status("192.168.1.100")
425+
426+
assert auth_state_cache.requires_auth("192.168.1.100") is True
427+
404428
async def test_it_marks_auth_required_before_a_later_read_can_fail(
405429
self, mock_rpc_client, mock_legacy_gateway
406430
):

packages/core/tests/unit/gateways/network/test_async_shelly_rpc_client.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
DeviceUnreachableError,
1010
)
1111
from core.gateways.network.async_shelly_rpc_client import AsyncShellyRPCClient
12+
from core.services.auth_state_cache import AuthStateCache
1213

1314

1415
class TestAsyncShellyRPCClient:
@@ -251,3 +252,31 @@ async def test_it_uses_default_timeout_when_none_provided(
251252
assert isinstance(timeout_arg, httpx.Timeout)
252253
assert timeout_arg.read == 3.0
253254
assert timeout_arg.connect == 2.0
255+
256+
257+
class TestAuthStateCacheGating:
258+
"""An empty AuthStateCache is falsy, so presence must decide these paths."""
259+
260+
async def test_it_clears_auth_state_while_the_cache_is_still_empty(self):
261+
auth_state_cache = AuthStateCache()
262+
client = AsyncShellyRPCClient(
263+
session=AsyncMock(spec=httpx.AsyncClient),
264+
auth_state_cache=auth_state_cache,
265+
)
266+
267+
client._invalidate_auth_cache("192.168.1.100")
268+
269+
assert auth_state_cache.is_known("192.168.1.100") is True
270+
assert auth_state_cache.requires_auth("192.168.1.100") is False
271+
272+
async def test_it_clears_auth_state_for_a_known_mac_while_the_cache_is_empty(self):
273+
auth_state_cache = AuthStateCache()
274+
client = AsyncShellyRPCClient(
275+
session=AsyncMock(spec=httpx.AsyncClient),
276+
auth_state_cache=auth_state_cache,
277+
)
278+
client._ip_to_mac["192.168.1.100"] = "AABBCCDDEEFF"
279+
280+
client._invalidate_auth_cache("192.168.1.100")
281+
282+
assert auth_state_cache.is_known("AABBCCDDEEFF") is True

0 commit comments

Comments
 (0)