Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
53 changes: 53 additions & 0 deletions test/test_home.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import tibber
from tibber.exceptions import (
InvalidLoginError,
SubscriptionFailedError,
WebsocketReconnectedError,
WebsocketTransportError,
Expand Down Expand Up @@ -520,6 +521,7 @@ async def test_rt_subscription_reconnects_when_no_data_received(
aiohttp.ClientError("boom"),
TimeoutError(),
ValueError("boom"),
InvalidLoginError(400, '"exp" claim timestamp check failed', "UNAUTHENTICATED"),
],
)
@patch("tibber.home.RESUBSCRIBE_WAIT_TIME", 0)
Expand Down Expand Up @@ -563,6 +565,57 @@ def callback(data: dict) -> None:
home.rt_unsubscribe()


@patch("tibber.home.RESUBSCRIBE_WAIT_TIME", 0)
@pytest.mark.asyncio
async def test_resubscribe_step_known_error_logs_without_traceback(
home: tibber.TibberHome,
mock_realtime: MagicMock,
mock_websession: MagicMock,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Known API errors (HttpExceptionError) must log without a traceback.

Unknown errors (e.g. ValueError) must still emit exc_info so the traceback
is preserved for debugging.
"""
# --- known error: no traceback ---
mock_websession.post.side_effect = InvalidLoginError(
400,
'"exp" claim timestamp check failed',
"UNAUTHENTICATED",
)
_, subscribe_fn = _make_blocking_subscribe([])
mock_realtime.subscribe = subscribe_fn

with caplog.at_level(logging.WARNING):
await home.rt_subscribe(MagicMock())

# The warning message contains the failure_message text; exc_info must be None.
known_records = [r for r in caplog.records if "keeping last known" in r.message]
assert known_records, "Expected warning records for the resubscribe step"
for rec in known_records:
assert rec.exc_info is None, "Known API error must not include traceback"

home.rt_unsubscribe()
caplog.clear()

# --- unknown error: traceback preserved ---
mock_websession.post.side_effect = ValueError("something unexpected")
_, subscribe_fn2 = _make_blocking_subscribe([])
mock_realtime.subscribe = subscribe_fn2

with caplog.at_level(logging.WARNING):
await home.rt_subscribe(MagicMock())

# exc_info must be set so the traceback is visible in logs for unexpected errors.
unknown_records = [r for r in caplog.records if "keeping last known" in r.message]
assert unknown_records, "Expected warning records for the resubscribe step"
for rec in unknown_records:
assert rec.exc_info is not None, "Unknown error must include traceback"

home.rt_unsubscribe()


@patch("tibber.home.RT_SUBSCRIPTION_TIMEOUT", 0)
async def test_rt_subscription_timeout_calls_on_error(
home: tibber.TibberHome,
Expand Down
5 changes: 4 additions & 1 deletion tibber/home.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from gql import gql

from .const import RESOLUTION_DAILY, RESOLUTION_HOURLY, RESOLUTION_MONTHLY, RESOLUTION_WEEKLY
from .exceptions import SubscriptionFailedError, WebsocketReconnectedError, WebsocketTransportError
from .exceptions import HttpExceptionError, SubscriptionFailedError, WebsocketReconnectedError, WebsocketTransportError
from .gql_queries import (
HISTORIC_DATA,
HISTORIC_DATA_DATE,
Expand Down Expand Up @@ -591,6 +591,9 @@ async def _resubscribe_step(self, coro: Awaitable[Any], failure_message: str) ->
except (TimeoutError, aiohttp.ClientError):
# Transport errors are already logged at debug level inside execute.
_LOGGER.warning("Home %s: %s", self.home_id, failure_message)
except HttpExceptionError as err:
# Known API errors (e.g. an expired token) are expected here and don't warrant a traceback.
_LOGGER.warning("Home %s: %s: %s", self.home_id, failure_message, err)
except Exception: # noqa: BLE001
_LOGGER.warning("Home %s: %s", self.home_id, failure_message, exc_info=True)

Expand Down