Commit Graph

11 Commits (8c297cf90cdb0d0905031b1e3527c75f044155b9)

Author SHA1 Message Date
SchiZzA 8c297cf90c
fix(review): fix(review): keep refused entities, split route errors, warn on stalled migration 2026-07-26 23:03:09 +02:00
SchiZzA 0400a9cdee feat(migration): adopt predecessor integration entities, keeping history
The integration is moving to a new repository under a new domain. Home Assistant
keys recorder history and long-term statistics on entity_id, not on the domain,
so the move is free provided the registry entries are carried over instead of
recreated. A recreated entity gets a fresh entity_id and its history is orphaned.

predecessor.py does the carry-over with async_update_entity_platform, which
rewrites platform and config_entry_id and leaves entity_id alone. Everything the
user set by hand rides along, because the registry row itself survives: renames,
icons, areas, labels, categories, hidden and disabled state.

Guards, each one covered by a test that fails when it is removed:

- The startup placeholder state (unavailable + ATTR_RESTORED) is cleared first,
  because async_update_entity_platform accepts only a missing or unknown state.
- A state that is not a placeholder means an integration is still driving the
  entity, so it is refused rather than moved out from under a live platform.
- A unique_id already taken under our own domain is refused. Home Assistant does
  not check this itself: the duplicate guard in _async_update_entity only runs
  when new_unique_id is passed.
- Devices are repointed before the old entry is removed. A device that loses its
  last config entry is deleted, and that takes its entities with it.
- The old config entry is removed only when every entity made it across.
  Otherwise it stays, a retry stays possible, and nothing is lost.

Options are inherited in a separate, earlier pass. Adopting the registry alone
is not enough: SENSORS_TO_LOAD decides which entities the platform creates, so
an empty one leaves every adopted entry showing "no longer provided" until a
payload arrives, and derived sensors are never in a payload at all. The merge is
predecessor-fills-gaps, so anything just entered in the config flow wins. The
version 1 spelling of the Pocasi Meteo flag is translated on the way in, since
async_migrate_entry runs against this entry's own version and would never look
at a key copied in afterwards.

The two passes run at different points in async_setup_entry. Options first,
before the coordinator and routes read them, or an inherited WSLINK flag would
leave the entry listening on the wrong endpoint until a reload. Adoption after
the routes are proven but before async_forward_entry_setups, so the one
destructive step cannot run in a setup that then fails, and so our platforms
bind to the adopted registry entries rather than to freshly created ones.

Two problems the migration surfaced, fixed here as well:

- register_path now catches ValueError alongside RuntimeError and raises
  ConfigEntryNotReady with a message naming HACS. aiohttp rejects a duplicate
  route name with ValueError, and the route names are fixed rather than
  domain-scoped, so a user who has not yet removed the old version hit an
  unhandled traceback.
- add_new_sensors applies the derived-sensor expansion that async_setup_entry
  already applied. Derived sensors are computed, never sent, so discovery can
  only offer their inputs; the azimut unlocked by a newly discovered wind
  direction went missing until the next restart. Only the sensors this batch
  unlocks are added, so nothing is created twice.

PREDECESSOR_DOMAIN equals DOMAIN for now, which short-circuits the whole pass.
It stays correct until the day the domain changes.
2026-07-26 22:08:01 +02:00
SchiZzA 3cdaffc81f
fix(review): parse battery flags via to_int, release routes on removal 2026-07-26 15:53:03 +02:00
SchiZzA d2ae26eab5
fix(review): persist WSLink probe types and harden flag parsing 2026-07-26 15:41:30 +02:00
SchiZzA 0dfdd6c9e9
fix(review): Disable stale routes and validate credentials 2026-07-26 12:03:36 +02:00
SchiZzA f6088e86ab
chore(quality): manifest keys, ConfigEntryNotReady, remove dead sqlite code
- manifest: add single_config_entry, integration_type "device", loggers
  ["aioecowitt"]; drop empty homekit/ssdp/zeroconf placeholders.
- Q10: raise ConfigEntryNotReady (not PlatformNotReady) when route registration
  fails in async_setup_entry; drop the now-unused import.
- Q11: delete the ~105-line commented-out sqlite3 statistics-migration block in
  utils.py (would have been blocking I/O in the event loop) and its dead import.

Deferred (with reason): quality_scale (needs a rule audit + quality_scale.yaml),
device-identifier 2-tuple and suggested_entity_id (entity-naming/identity changes,
to land with the planned integration rename), CoordinatorEntity generics in
sensor.py (would introduce a circular import - intentionally untyped).
Note: the health-probe protocol gate was reverted - the proxy add-on can front
WU/WSLink/Ecowitt, so polling must stay unconditional.

308 passing, 100% coverage; ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 21:47:52 +02:00
SchiZzA 14466cbe60
fix(correctness): reload-window guards, rate-limit races, ecowitt flush, dt_util
- C1: received_data / received_ecowitt_data return 503 (not 500) when the entry is
  mid-reload (runtime_data gone); add_new_sensors / add_new_binary_sensors are
  defensive no-ops in that window too.
- C2/C3: Windy and Pocasi reserve their next send window *before* the await, so
  concurrent webhooks can no longer both pass the rate-limit check and double-send
  (which could falsely trip the auto-disable threshold).
- C5: EcowittBridge.set_add_entities flushes unmapped sensors parsed before the
  platform was ready (aioecowitt fires new_sensor_cb only once per key).
- C6: a forwarder auto-disabling itself from the hot path no longer forces a full
  config-entry reload (update_listener now skips reload for live-read flags
  SENSORS_TO_LOAD / WINDY_ENABLED / POCASI_CZ_ENABLED).
- C7: to_int accepts decimal-formatted integers ("180.0").
- C8: forwarders use dt_util.utcnow() (UTC-aware) instead of naive datetime.now().

Tests updated; 305 passing, 100% coverage; ruff + basedpyright clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 21:47:52 +02:00
SchiZzA 526d5e4f6e
test: reach 100% coverage of custom_components/sws12500
Add focused test modules and extend existing ones to cover every line of the
integration (1588 stmts, 0 missing; 295 tests):

- test_diagnostics.py: redaction + health_data fallback.
- test_binary_battery.py: binary_sensor platform setup / add_new + BatteryBinarySensor.
- test_staleness_legacy.py: warmup/threshold stale detection + legacy orphan issue.
- test_ecowitt_bridge.py: EcowittBridge parsing/discovery + EcoWittNativeSensor.
- test_health.py: HealthCoordinator (online/offline refresh, summary, ingress,
  forwarding, HTTP endpoint) + HealthDiagnosticSensor.
- test_received_ecowitt.py: received_ecowitt_data paths + received_data health branches.
- test_windy_more.py: duplicate (409) / rate-limit (429) / 3-strike disable.
- test_routes_more.py: RouteInfo.__str__, ingress observer, canonical resolve.
- test_utils_conv.py: to_int/to_float edge cases.
- config_flow: wslink_port_setup step + initial ecowitt flow.
- init: _check_stale time-interval callback.
- lifecycle: register_path idempotency (existing-dispatcher branch).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 21:47:51 +02:00
SchiZzA 4dcd76da62
test: align suite with v2.0 runtime_data architecture
The suite predated the coordinator extraction and the typed runtime_data
migration: 5 modules failed to import and 18 tests failed.

- Import IncorrectDataError/WeatherDataUpdateCoordinator from .coordinator.
- Point received_data monkeypatch targets at custom_components.sws12500.coordinator.*
- Stub config entries with async_on_unload + runtime_data (SWSRuntimeData).
- Rewrite test_data, test_sensor_platform and test_integration_lifecycle off the
  removed ENTRY_* hass.data keys onto runtime_data (route dispatcher reuse, ecowitt
  POST route, no hass.data pop on unload, health first_refresh mocked).
- Update config_flow tests for the user menu (pws step) and the options menu
  gaining wslink_port_setup.
- Fix VOCLevel.EXCELENT typo and stale t9 expectations (tuple battery list,
  HCHO int coercion, connection-gated subset).

Result: 168 passed, 0 collection errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 21:47:51 +02:00
SchiZzA f0d24ebde3
Make routes method-aware and update related tests
Include HTTP method in route keys and dispatch, and fix
Routes.show_enabled.
Update register_path to accept a HealthCoordinator and adjust router
stubs in tests. Update WindyPush tests to use response objects
(status/text)
and adapt related exception/notification expectations.
2026-07-25 21:47:47 +02:00
SchiZzA 0060369d8a
Add health diagnostic sensor and extensive tests for sws12500
integration

- Introduce HealthDiagnosticSensor for device health status reporting
- Add new constants and data keys for health sensor integration
- Wire health_sensor module into sensor platform setup
- Refactor sensor descriptions to improve derived sensor handling
- Implement pytest fixtures and comprehensive tests covering:
  - Config flows and options validation
  - Data reception and authentication
  - Sensor platform setup and dynamic sensor addition
  - Push integration with Pocasi.cz and Windy API
  - Route dispatching and error handling
  - Utilities, conversions, and translation functions
2026-07-25 21:45:08 +02:00