Release v2.4.2 - #7
Conversation
Two washer/dryer entities showed as "unavailable" with the orphan "?" badge after commit c860952 (shipped publicly in 2.4.0): the "Programma" select and the "Alimentazione" power switch. Both were left in the entity registry but no longer provided by the integration. Program select: - source the option list from startProgram too, so appliances that only expose the program via startProgram get the select back (and the existing `<id>_program` registry entry rebinds, no longer unavailable). - decouple selection from start: selecting a program now only records the choice (shared store on the coordinator) without sending any command, so picking an option never starts the appliance. - current_option reflects the pending choice, then real device state (prCode/program/programName). Start button: - "Avvia programma" applies the program chosen in the select before sending startProgram, then clears the pending choice so the select reflects device state again. Legacy power entity: - remove orphan `<id>_power` registry entries on setup so the misleading "Alimentazione" switch (intentionally removed) stops showing as unavailable. Adds tests/test_program_select.py covering select creation for startProgram-only appliances, no-start-on-select, button applying/clearing the pending program, device-state fallback, and the power cleanup.
- select.current_option: also match device attributes that expose a human program label (e.g. programName) instead of a code, so the current program is shown regardless of whether the device reports code or label. - button "Avvia programma": fail safe when the pending program cannot be attached to startProgram (no program/prCode parameter) by raising instead of starting a default cycle, and keep the pending selection so it is not lost. - centralize the pending-store key in const.PROGRAM_PENDING_STORE (was duplicated in select.py and button.py). - tests: restore globally patched entity_registry functions via addCleanup, add coverage for human-label current_option and the non-applicable-program fail-safe.
|
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 (7)
📝 WalkthroughWalkthroughThe integration refactors program selection for Haier appliances using a pending-program pattern: the select entity now records user choices in coordinator-backed storage without sending commands, while the start button applies the pending selection to the ChangesProgram Selection Refactor with Legacy Cleanup
Sequence Diagram(s)Program selection end-to-end flow showing how the select entity stores pending choices and the button applies them: sequenceDiagram
participant User
participant SelectEntity
participant Button
participant CoordinatorStore as Coordinator<br/>Store
participant Command
participant Device
User->>SelectEntity: Select program option
SelectEntity->>CoordinatorStore: Store program code in<br/>PROGRAM_PENDING_STORE
SelectEntity->>SelectEntity: Update UI (async_write_ha_state)
Note over CoordinatorStore: Pending selection persists<br/>across coordinator refreshes
User->>Button: Press "Avvia programma"
Button->>CoordinatorStore: Read pending program
Button->>Command: Apply pending code to<br/>startProgram parameter
Command->>Device: Send command with program
Device-->>Button: Command executed
Button->>CoordinatorStore: Clear pending program
Button->>Button: Refresh coordinator state
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
…2xx) Audit findings #15 and #7. refresh() read data["id_token"]/data["access_token"] directly, raising KeyError on a 2xx response missing them (#7), and never persisted a rotated refresh_token from the response (#15), so a rotating IdP would invalidate the stored token and force a full re-login. Read tokens defensively: a 2xx without id_token/access_token now returns False (caller falls back to authenticate()) without touching _expires; a rotated refresh_token in the response is persisted. Tests: rotation, no-rotation, malformed-2xx->False, 4xx->False.
…2xx) Audit findings #15 and #7. refresh() read data["id_token"]/data["access_token"] directly, raising KeyError on a 2xx response missing them (#7), and never persisted a rotated refresh_token from the response (#15), so a rotating IdP would invalidate the stored token and force a full re-login. Read tokens defensively: a 2xx without id_token/access_token now returns False (caller falls back to authenticate()) without touching _expires; a rotated refresh_token in the response is persisted. Tests: rotation, no-rotation, malformed-2xx->False, 4xx->False.
…2xx) Audit findings #15 and #7. refresh() read data["id_token"]/data["access_token"] directly, raising KeyError on a 2xx response missing them (#7), and never persisted a rotated refresh_token from the response (#15), so a rotating IdP would invalidate the stored token and force a full re-login. Read tokens defensively: a 2xx without id_token/access_token now returns False (caller falls back to authenticate()) without touching _expires; a rotated refresh_token in the response is persisted. Tests: rotation, no-rotation, malformed-2xx->False, 4xx->False.
) - 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.
…ges, #7 label) - CodeRabbit #5: add the empty-string dryLevel sentinel to DRY_LEVEL_SENTINELS ('' / '0' / '11' are all "no dry level" per the app's hasDryLevelValue), so a blank code is dropped from the select options and does not count toward the gate. - CodeRabbit #6: the range materializer no longer overshoots the max. option_choices used `current <= hi + step/2`, which could emit a value beyond the declared max on a step that overshoots (0..10 step 20 -> ["0","20"]); it now uses a tight `+1e-9` bound (float-drift only). is_settable_option for a drop-free range now requires `lo + step <= hi` (>= 2 reachable values) instead of `max > min`, so a single-real- value range is not offered as a control. (A defensive step<=0 guard is added too; param_range already coerces 0->1 and rejects negatives, so it is unreachable in practice.) No real Haier range is affected (all integer, on-grid max). - CodeRabbit #7: give anti_crease_time a label distinct from anticrease (en "Anti-crease time", it "Tempo antipiega") so a WD exposing both does not show two identical switches. Tests added (mutation-proven): uneven-range no-overshoot + gate, '' sentinel drop, anti_crease_time/anticrease label distinctness. 3-lens refuter pool: HOLD. 907 passed, 1 skipped (local + CI-clean).
Automated release PR for
v2.4.2.Merge this PR with squash only. The post-merge workflow will move
devto the squash commit, recreatev2.4.2on that commit, and publish the release.Summary by CodeRabbit
Release Notes
Bug Fixes
New Features
Version: 2.4.2