Skip to content

fix: 8 bug fixes — sensor data loss, SSL bypass, JSON injection, ZeroDivisionError, and more#145

Merged
KJonline merged 29 commits into
devfrom
bug-analysis
Jun 20, 2026
Merged

fix: 8 bug fixes — sensor data loss, SSL bypass, JSON injection, ZeroDivisionError, and more#145
KJonline merged 29 commits into
devfrom
bug-analysis

Conversation

@KJonline

Copy link
Copy Markdown
Contributor

Summary

  • Sensor data loss (sensor.py:160): HIVE_TYPES["Sensor"] branch was keying into data.devices with hive_id instead of device_id, causing device_data, props, and parent_device to always be empty for contact/motion sensors
  • Security (hive_async_api.py): Removed verify=False SSL bypass and urllib3.disable_warnings; replaced string-concatenated JSON payload in set_state with json.dumps to eliminate injection risk
  • Crash on zero (color.py): Colour-temperature conversion methods silently swallowed KeyError but not ZeroDivisionError — added ZeroDivisionError to all three except clauses
  • None dereference (hive_auth_async.py): async_init and get_password_authentication_key called .split() on values from .get() without guarding against None; now raises HiveUnknownConfiguration with a descriptive message
  • Broken epoch_time (hive_helper.py): The to_epoch branch hardcoded its own pattern, ignoring the pattern argument passed by the caller
  • updateInterval no-op (compat_aliases.py): The HA backwards-compat alias was silently returning True without updating config.scan_interval; now correctly sets timedelta(seconds=new_interval)
  • Dead code removed (srp_crypto.py, hive_async_api.py): Unused ThreadPoolExecutor pool (spawned threads on import), deprecated refresh_tokens method and its tests, dead if url is not None guard
  • Log context (heating.py, hotwater.py, boost.py): Bare _LOGGER.error(e) calls now include method name and device name

Test Plan

  • pytest tests/ — 956 passed, 99.17% coverage (above 99% threshold)
  • One pre-existing failure in test_polling.py::TestGetDevicesSlowPoll::test_auth_error_sets_last_poll_slow_false — predates this branch, unrelated to these changes
  • New unit tests added for every fix (sensor device_id key, None guards, ZeroDivisionError paths, epoch_time pattern, json.dumps encoding, updateInterval timedelta)

🤖 Generated with Claude Code

KJonline and others added 27 commits May 23, 2026 16:37
- Replace __class__.__name__ string checks with direct error.response parsing for boto3 ClientError
- Remove redundant if checks when re-raising EndpointConnectionError as HiveApiError
- Defer asyncio.get_event_loop() call to async_init() using get_running_loop()
- Remove deprecated pool_region parameter from HiveAuthAsync.__init__
- Add HiveError base class and reorganize exception hierarchy (HiveConfigurationError, HiveAuthCredentialError)
…VE_TYPES branch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… get_password_authentication_key

Import HiveUnknownConfiguration and raise it instead of letting AttributeError
propagate when REGION or UPID are absent from the SSO login info response, and
when _pool_id is None or missing an underscore in get_password_authentication_key.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extend the except clause in get_min_color_temp, get_max_color_temp, and
get_color_temp from KeyError-only to (KeyError, ZeroDivisionError) so that
a zero colourTemperature value returned by the Hive API returns None instead
of raising an unhandled ZeroDivisionError. Tests added for all three cases.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…hardcoding it

Removes the line that overwrote the caller-supplied `pattern` with a
hardcoded Hive format string, so custom format strings are respected.
Adds TestEpochTimePattern tests to confirm the fix and prevent regression.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…n.dumps in set_state

- Replace manual string-concatenation JSON building in set_state with json.dumps(kwargs)
  to prevent JSON injection when kwarg values contain double-quotes or backslashes
- Remove requests.get(verify=False) SSL bypass from get_login_info
- Remove urllib3 import and disable_warnings call that suppressed the SSL warning
- Update TestGetLoginInfo assertion to match new call signature (no verify=False)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nd dead url/status guard

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… returning True

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… SessionConfig type

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nst null

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Unhandled ClientError codes in initiate_auth and respond_to_auth_challenge
were silently swallowed, leaving response=None and crashing later with
TypeError. async_init would AttributeError on None when get_login_info
returned no data. All three now raise HiveApiError/HiveUnknownConfiguration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…n access

update_tokens() elif branch never set token_created (leaving it at
datetime.min) and crashed on KeyError when refreshToken was absent.
hive_refresh_tokens() used bare dict access for refreshToken. Both now
use safe access patterns and update token_created consistently.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
close() previously closed the websession unconditionally. Added
_owns_websession flag (True when websession=None at construction time)
so callers who inject their own session retain ownership and lifecycle
control.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…twater)

Bare dict access data["props"]["previous"]["mode"] raised KeyError when
previous was absent, silently swallowed by the outer except and logged
as an error. Replaced with _get_product_state which returns None safely
without triggering the error log path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When props.previous.mode is absent, set_boost_off was calling
_execute_state_change(mode=None) causing a silent API failure.
Now returns False early with a warning when previous mode cannot
be determined.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…s_color

Brightness calculation returned a float (127.5 for 50%), but HA light
entities require int. get_color returned an RGB 3-tuple (r,g,b) but
HA hs_color expects (hue_degrees, saturation_percent) — Hive API stores
these natively so no conversion is needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…y device data

Empty device/product data after polling means configuration is unknown,
not that tokens expired — using HiveReauthRequired wrongly triggered
a full re-login. Also fixed bare d["id"]/p["id"] access in create_devices
to use .get() with a fallback key to avoid KeyError when the id field
is absent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- confirm_device: non-CodeMismatch ClientErrors now raise HiveApiError
  instead of being silently swallowed
- forget_device: NotAuthorizedException now raises HiveApiError instead
  of the misleading HiveInvalid2FACode; all ClientErrors propagate
- generate_hash_device and get_device_authentication_key are pure
  computation with no I/O — converted from async def to def; callers
  updated to remove await

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rd hotwater schedule NNL

- get_schedule_nnl: slot dicts are now shallow-copied before Start_DateTime
  is added, preventing in-place mutation of the original schedule data that
  caused stale results on repeated calls
- hotwater.get_state: guard snan["now"] access so an empty NNL (fewer than
  3 schedule slots) does not raise KeyError; state falls back to the base
  status value instead

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ix Map.__delattr__

- SessionConfig.battery and .mode changed from list to set; discovery.py
  callers updated from .append() to .add() for O(1) membership checks
- HiveAttributes.get_mode: removed no-op HIVETOHA["Attribute"] lookup —
  mode strings never matched boolean keys so the lookup always passed
  through; now returns raw mode value directly
- HiveAttributes.get_battery: removed dead error_check call — battery state
  is always an integer, never False or "Failed", so error_check never
  triggered a meaningful action
- Map.__delattr__: was raising KeyError for missing keys via dict.__delitem__;
  now wraps in AttributeError to match Python's attribute protocol

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- update_tokens elif-'token' branch with update_expiry_time=False: token_created
  must not be updated (106->109 branch was uncovered)
- get_devices with homes_data as a non-dict (list): must not crash and home_id
  stays unset (196->181 branch was uncovered)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The method was never called by any production or test code and was
explicitly marked as superseded by AWS token management. Removing it
brings coverage to 100%.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Deleted ~20 tests from test_remaining_branches.py that were exact
duplicates of tests in their dedicated _extended files. Moved the two
genuinely unique tests (get_state exception handler and dict-under-
sensitive-key sanitize path) to test_heating_extended.py and
test_helpers.py respectively. Coverage remains at 100%.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Moves all remaining unique test classes from test_remaining_branches.py
into their respective device/session/helper extended files. Deletes the
empty catch-all. Coverage remains at 100%.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
test_schedule_mode_returns_nnl, test_motionsensor_returns_motion_status,
and test_empty_devices_after_get_devices_raises_unknown_configuration were
identical (same mocked setup, same assertions) to their counterparts in
the corresponding unit/_extended files. Removed from module files; unit
versions retained. Coverage remains at 100%.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Renamed 9 standalone extended files (git mv) and merged 4 extended files
into their base counterparts, deduplicating helpers and consolidating all
test classes under clean names with no _extended suffix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread tests/unit/test_hotwater.py Fixed
@codecov

codecov Bot commented Jun 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.96%. Comparing base (3838609) to head (7908dbd).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##              dev     #145      +/-   ##
==========================================
- Coverage   99.96%   99.96%   -0.01%     
==========================================
  Files          30       30              
  Lines        2594     2508      -86     
  Branches      293      292       -1     
==========================================
- Hits         2593     2507      -86     
  Partials        1        1              
Flag Coverage Δ
unittests 99.96% <100.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/__init__.py 100.00% <ø> (ø)
src/api/device_registration.py 100.00% <100.00%> (ø)
src/api/hive_api.py 100.00% <100.00%> (ø)
src/api/hive_async_api.py 100.00% <100.00%> (ø)
src/api/hive_auth_async.py 100.00% <100.00%> (ø)
src/api/srp_crypto.py 100.00% <ø> (ø)
src/devices/boost.py 100.00% <100.00%> (ø)
src/devices/color.py 100.00% <100.00%> (ø)
src/devices/heating.py 100.00% <100.00%> (ø)
src/devices/hotwater.py 100.00% <100.00%> (ø)
... and 13 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

- Extracted _call_endpoint helper in both sync/async API classes to eliminate
  repetitive try/except blocks across get_all, get_devices, get_products,
  get_actions, motion_sensor, get_weather, set_state, and set_action
- Removed get_login_info from HiveApiAsync (unused; sync version retained)
- Replaced manual JSON string building in set_state with json.dumps
- Replaced fragile string manipulation for SSO parsing with regex in
Comment thread tests/unit/test_hotwater.py Fixed
@KJonline KJonline merged commit e3e70d0 into dev Jun 20, 2026
10 checks passed
@KJonline KJonline deleted the bug-analysis branch June 20, 2026 08:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant