Release v2.4.3 - #8
Conversation
The release pipeline pinned everything to a frozen release-source-sha captured at tag time, which broke as soon as dev advanced during an open release PR (e.g. pushing CodeRabbit fix commits): - release-guard required release-source-sha == PR head and fetched the exact tag, so a fix commit failed it with "dev advanced..." or, when a concurrent intake/post-merge run was mid delete+recreate of the tag, with the cryptic "fatal: couldn't find remote ref refs/tags/<tag>" (exit 128). - post-merge refused to sync dev when origin/dev != the frozen source-sha, leaving dev behind main after a release (manifest stuck on the old version). Switch both to validating the LIVE state instead of a frozen commit: - release-guard: drop the source-sha pin and the tag fetch; just require the manifest version on origin/dev to match the release tag. Also allow marker-less maintenance dev -> main PRs through (post-merge already handles the no-release case). - post-merge: gate the dev sync on tree equality (merge tree == origin/dev tree) instead of sha equality, and lease on the freshly observed dev. This tolerates review fix commits (which rewrite history) while still refusing to clobber dev if it carries content the squash does not include. The published tag is created on the squash commit at merge time, as before. Document the new "push review fixes straight to dev" flow.
The update loop already calls load_statistics(), but _get_attributes() only merged appliance.attributes (+parameters) and appliance.settings, never appliance.statistics. The lifetime/cycle consumption counters (totalElectricityUsed, totalWaterUsed, totalWashCycle, currentElectricityUsed, currentWaterUsed) live in the statistics container, so the consumption sensors read None and showed as unknown. Merge appliance.statistics first (lowest precedence) so real-time attributes and settings still win on any key collision. Add tests covering the merge, the conflict precedence, and the missing-statistics case.
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
📝 WalkthroughWalkthroughThis PR adds appliance statistics extraction to the Home Assistant component's attribute gathering, introduces corresponding test coverage, and refactors release workflow automation to validate against live dev-branch state with stricter gates and reordered release checks, including updated documentation. ChangesStatistics Feature Enhancement
Release Workflow Refinement
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Audit finding #8. The 11 await resp.json() call-sites in api.py omitted content_type=None, while connection._intercept and auth.py use it deliberately because the hOn/Salesforce cloud sometimes returns valid JSON with a non-JSON Content-Type. A response that passes _intercept (content_type=None) could then raise aiohttp.ContentTypeError in api.py, crashing load_*/send_command. Align all call-sites to resp.json(content_type=None). Test: a strict response that rejects json() without content_type=None, exercised through load_appliances and load_commands.
Audit finding #8. The 11 await resp.json() call-sites in api.py omitted content_type=None, while connection._intercept and auth.py use it deliberately because the hOn/Salesforce cloud sometimes returns valid JSON with a non-JSON Content-Type. A response that passes _intercept (content_type=None) could then raise aiohttp.ContentTypeError in api.py, crashing load_*/send_command. Align all call-sites to resp.json(content_type=None). Test: a strict response that rejects json() without content_type=None, exercised through load_appliances and load_commands.
Audit finding #8. The 11 await resp.json() call-sites in api.py omitted content_type=None, while connection._intercept and auth.py use it deliberately because the hOn/Salesforce cloud sometimes returns valid JSON with a non-JSON Content-Type. A response that passes _intercept (content_type=None) could then raise aiohttp.ContentTypeError in api.py, crashing load_*/send_command. Align all call-sites to resp.json(content_type=None). Test: a strict response that rejects json() without content_type=None, exercised through load_appliances and load_commands.
) - oauth._remote_descriptor: defensive parse (bad JSON, non-dict payload, a non-numeric or overflowing `ver`) so a malformed Salesforce descriptor stays inert -- detection falls back to None instead of raising a hard login failure (#8). - oauth.detect_progressive_otp: return None when the ViewState id ("vid") is absent, mirroring the csrf/authorization guard; an empty vid is sent in every /apexremote ctx and would be rejected with a confusing error (#7). - config_flow._async_close_mfa_client: also clear _mfa_data (the cached plaintext password) and _mfa_reauth_entry, so credentials/state are not left reachable on the flow object after success, abort, or async_remove (#9). - hon_client: thread the recorded phase into classify(err, phase=...) at the setup / MFA-verify / MFA-send failure sites (defensive; no auth phase is timeout-mapped today, so it is future-proofing of the diagnostics wiring) (#10). - tests: RUF059 unused-unpack rename; mutation-proven coverage for each fix.
…els + harden test stub - Greptile P2 (duplicate labels lose codes): HonProgramOptionSelect built a reverse label->raw map that collapsed when two exposed raw codes shared a label (DRY_LEVEL_LABELS_TD maps e.g. 1 & 12 both to "iron_dry"), making one code unreachable and breaking the select round-trip on a device exposing both tiers. __init__ is now collision-aware: only the colliding labels are suffixed with their raw code, so every exposed code stays selectable and the reverse map is injective. Non-colliding labels are untouched, so the common case (every real WM + erpayo's TD dryLevel[12,13,14]) is byte-for-byte unchanged and keeps its translatable state keys. - CodeRabbit (#8): behavioral tests that a device exposing label-colliding codes keeps both selectable and round-trippable, plus a no-collision guard (mutation-proven). - CodeRabbit (#9/#10, partial): completed test_wash_option_params.py's HA stub (force-assign a complete CoordinatorEntity with hass/async_write_ha_state/available, mirroring test_ac_write_path.py) so the module is collection-order-robust and never poisons other entity-constructing modules. The repo-wide conftest cleanup is left as a separate follow-up. Built via 3 convergent constructors + 3-lens refuter pool to HOLD. 904 passed, 1 skipped (local + CI-clean). The duplicate-label fix is theoretical on known models (no real device exposes both dryLevel tiers); shipped as forward robustness.
CodeRabbit: test_catalog_keys_and_params_unique_within_type guarded only param uniqueness, though its comment also claimed to cover unique_id-suffix collisions. The option entity unique_id is f"{appliance_id}_opt_{key}" scoped per platform, so two controls of the same platform sharing a key collide. Add a per-platform key uniqueness assertion (cross-platform key reuse is fine -- different entity domains). Mutation-proven: a duplicate key with a distinct param fails the key check while the param check still passes. 907 passed, 1 skipped.
Automated release PR for
v2.4.3.Merge this PR with squash only. The post-merge workflow will move
devto the squash commit, recreatev2.4.3on that commit, and publish the release.Summary by CodeRabbit
New Features
Chores