Skip to content

fix(coordinator): 🐛 validate serial number instead of masking missing data#673

Merged
rhammen merged 1 commit into
mainfrom
fix/coordinator-hardening
Jul 5, 2026
Merged

fix(coordinator): 🐛 validate serial number instead of masking missing data#673
rhammen merged 1 commit into
mainfrom
fix/coordinator-hardening

Conversation

@rhammen

@rhammen rhammen commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

🐛 Summary

Follow-up hardening from a review of coordinator.py, focused on failing loudly instead of masking problems:

  • 🧹 Removed the unused catch_luxtronik_errors decorator — it was defined and had tests, but was never applied anywhere, so it protected nothing.
  • 🔌 async_shutdown now explicitly closes the connection via a new public Luxtronik.disconnect() (run in an executor job), instead of del-ing the client and hoping GC/__del__ closes the socket before shutdown completes.
  • 🚧 Split write/identification failures out of UpdateFailed into dedicated LuxtronikWriteError / LuxtronikSerialNumberError exceptions — UpdateFailed is meant for a failed update_method, not an arbitrary write or a missing serial number.
  • 🛡️ get_sensor() now tolerates coordinator.data being unpopulated (returns None) instead of raising AttributeError.
  • 🔑 serial_number now raises LuxtronikSerialNumberError instead of silently falling back to "" when the date parameter is missing — since it feeds unique_id, an empty fallback risks colliding device/entry identifiers between two different heat pumps.
  • 🔄 connect_and_get_coordinator() now checks last_update_success after the config-flow path's async_refresh() (which swallows failures, unlike async_config_entry_first_refresh()), converting a silently-failed initial refresh into the existing, well-handled LuxtronikConnectionError — so problems surface at the connection step rather than later as a confusing serial-number error.
  • 🌐 config_flow now catches LuxtronikSerialNumberError everywhere coordinator.unique_id is read (select_devices, manual_entry, dhcp, reconfigure) and shows a dedicated cannot_identify abort/error message (added to all 5 locales) instead of a generic "unknown" error.

✅ Test plan

  • pytest — 753 passed
  • pytest --cov=custom_components.luxtronik2 — 100% coverage (including the previously-untested except AbortFlow: raise guard in async_step_dhcp)
  • ruff check / ruff format --check — clean
  • basedpyright — 0 errors
  • Verified the DHCP reload_on_update=False safeguard (which prevents HA's "has an update listener and should use it for scheduling a reload" deprecation warning) is untouched, and added a regression assertion for it

🤖 Generated with Claude Code

… data

- Remove the unused catch_luxtronik_errors decorator (defined and tested,
  but never wired into any write path) instead of leaving dead code that
  looked like it protected something.
- Make async_shutdown explicitly disconnect the client via a public
  Luxtronik.disconnect() (executor job), instead of relying on `del` +
  GC/__del__ timing to eventually close the socket.
- Split write and identification failures out of UpdateFailed into
  dedicated LuxtronikWriteError / LuxtronikSerialNumberError exceptions,
  since UpdateFailed is meant to signal a failed update_method, not an
  arbitrary write or a missing serial number.
- get_sensor() now tolerates coordinator.data being unpopulated instead of
  raising AttributeError; serial_number raises LuxtronikSerialNumberError
  instead of silently falling back to "" - since it feeds unique_id, an
  empty fallback risks colliding device/entry identifiers.
- connect_and_get_coordinator() now checks last_update_success after the
  config-flow path's async_refresh() (which swallows failures, unlike
  async_config_entry_first_refresh()), converting a silent failed refresh
  into the existing, well-handled LuxtronikConnectionError.
- Propagate LuxtronikSerialNumberError through config_flow with a
  dedicated "cannot_identify" abort reason/form error (all 5 locales),
  instead of surfacing as a generic "unknown" error.
- Add regression test coverage for the above, plus the previously
  untested `except AbortFlow: raise` guard in async_step_dhcp and the
  reload_on_update=False safeguard that keeps DHCP rediscovery from
  re-triggering HA's "has an update listener" reload deprecation warning.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Code Coverage

Package Line Rate Health
custom_components.luxtronik2 100%
Summary 100% (2881 / 2881)

@rhammen rhammen merged commit 5fd708d into main Jul 5, 2026
9 checks passed
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