Skip to content

feat(config_flow): per-provider reauth flows (Phase 8 PR-5)#90

Merged
Artic0din merged 2 commits into
feat/comparator-pricing-modefrom
feat/reauth-flows
May 22, 2026
Merged

feat(config_flow): per-provider reauth flows (Phase 8 PR-5)#90
Artic0din merged 2 commits into
feat/comparator-pricing-modefrom
feat/reauth-flows

Conversation

@Artic0din
Copy link
Copy Markdown
Owner

Summary

PR-5 — First plank of HACS Silver. When Amber, LocalVolts, or OpenElectricity (DWT-OE) rejects an API key, PriceHawk raises ConfigEntryAuthFailed and HA prompts the user to re-enter credentials via the integration UI. Closes the loop that today produces silent WARNING spam (Amber / LocalVolts) or a stuck setup (DWT-OE raises but no reauth handler picks it up).

  • coordinator.py: Amber _fetch_amber_with_retry raises on 401/403 (was silently returning None); LocalVolts boundary translates LocalVoltsAPIError("auth failed") to ConfigEntryAuthFailed while preserving non-auth LocalVoltsAPIError semantics for other consumers; DWT-OE tags the failed provider before re-raising the existing PR-2 ConfigEntryAuthFailed.
  • New _reauth_provider_id instance attribute on the coordinator — single source of truth for "which provider failed last." Set BEFORE raising; read by the ConfigFlow dispatcher via entry.runtime_data.coordinator (Phase 7 PR-1 typed runtime data path).
  • config_flow.py: new async_step_reauth(entry_data) dispatcher routes to one of three per-provider sub-steps: async_step_reauth_amber, async_step_reauth_localvolts, async_step_reauth_dwt_oe. Unknown tag aborts cleanly with reason="reauth_provider_unknown".
  • Each sub-step pre-fills existing fields, validates the new credential against a live probe (Amber /v1/sites, LocalVolts fetch_recent_intervals, OpenElectricity SDK fetch_current_price), and calls async_update_reload_and_abort on success. Entry id, options, and runtime_data accumulators (daily_cost_history, _saving_month_aud, _daily_wins) are preserved.
  • API keys NEVER appear in log messages, UI errors, or exception strings — extends the existing PR-2 OpenElectricity scrubber pattern. Test test_amber_auth_failed_message_does_not_contain_key uses caplog to verify.
  • strings.json + translations/en.json byte-identical mirror with three new reauth step descriptions, invalid_credentials error, and reauth_provider_unknown / reauth_successful abort reasons.

Stacked on PR #89 (Phase 7 PR-4). GitHub auto-retargets up the chain (PR #86#87#88#89 → this) as Phase 7 merges.

Test plan

  • pytest --tb=short -q → 910 passed (884 baseline + 26 new)
  • ruff check coordinator.py config_flow.py tests/test_reauth.py → clean
  • diff strings.json translations/en.json → byte-identical
  • Manual UAT via Playwright on homeassistant.local:8123:
    • Revoke active Amber API key → entry transitions to "Reauthorize" toast/repair → reauth flow accepts a new key
    • Confirm sensor.pricehawk_today_cost continues from yesterday's accumulated value (NOT reset to 0)
    • LocalVolts: rotate partner ID → multi-field reauth form pre-fills + accepts new combo
    • DWT-OE: invalid-key path surfaces invalid_api_key error (skip if no waitlisted OE key available)

Decisions: DECISIONS.md > D-P8-1.

Closes: Phase 8 / PR-5. Foundation for PR-6 reconfigure (consumes same dispatcher pattern).

PR-5 — When Amber, LocalVolts, or OpenElectricity (DWT-OE) rejects an
API key (HTTP 401/403), PriceHawk now raises ConfigEntryAuthFailed and
HA prompts the user to re-enter credentials via the integration UI.
First plank of HACS Silver compliance.

- coordinator.py: Amber _fetch_amber_with_retry raises on 401/403 (was
  silently returning None). LocalVolts _maybe_poll_localvolts translates
  LocalVoltsAPIError("auth failed") to ConfigEntryAuthFailed at the
  coordinator boundary; non-auth LocalVoltsAPIError re-raises as-is.
  DWT-OE _refresh_dwt_price tags the failed provider before re-raising
  the existing ConfigEntryAuthFailed from PR-2.
- New _reauth_provider_id instance attribute on the coordinator —
  single source of truth for "which provider failed last." Set BEFORE
  raising; read by the ConfigFlow dispatcher.
- config_flow.py: new async_step_reauth(entry_data) dispatcher reads
  the tag via entry.runtime_data.coordinator and routes to one of:
  async_step_reauth_amber, async_step_reauth_localvolts, or
  async_step_reauth_dwt_oe. Unknown tag aborts cleanly with
  reason=reauth_provider_unknown.
- Each sub-step pre-fills the existing entry's field(s), validates the
  new credential against a live probe (Amber /sites, LocalVolts
  fetch_recent_intervals, OpenElectricity SDK), and calls
  async_update_reload_and_abort on success. Entry id, options, and
  runtime_data accumulators (daily_cost_history, _saving_month_aud,
  _daily_wins) are preserved through the reauth.
- API keys NEVER appear in log messages, UI errors, or exception
  strings. Extends the PR-2 OpenElectricity scrubber pattern.
- strings + translations: new reauth_amber / reauth_localvolts /
  reauth_dwt_oe step descriptions + invalid_credentials error +
  reauth_provider_unknown / reauth_successful abort reasons.
  strings.json byte-identical to translations/en.json.
- 26 new tests covering raise sites, key redaction (with caplog),
  dispatcher source contracts, sub-step source contracts, strings
  parity, and history-preservation guard.
- 910 total pass (884 baseline + 26 new).

Decisions: DECISIONS.md > D-P8-1 (dispatcher-pattern reauth chosen over
ConfigEntryAuthFailed subclassing — HA strips the exception before
invoking reauth, only entry id survives the round-trip).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Artic0din Artic0din merged commit 9afb917 into feat/comparator-pricing-mode May 22, 2026
2 of 4 checks passed
@Artic0din Artic0din deleted the feat/reauth-flows branch May 22, 2026 05:39
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