mirror of
https://github.com/basnijholt/adaptive-lighting.git
synced 2026-09-20 02:34:05 +02:00
Test infrastructure: - Add `pytest-homeassistant-custom-component` (PHACC) as a `test` dependency group. PHACC ships `hass`, `enable_custom_integrations`, `MockConfigEntry`, and friends without needing to clone HA core as a sibling directory. Modernizes the test setup from upstream's `setup-symlinks` pattern. - conftest.py: add `auto_enable_custom_integrations` autouse fixture so HA discovers the integration under `custom_components/` during tests. Keep the upstream template-deprecation no-op. - Import paths: all tests now import from `custom_components.adaptive_lighting` (not `homeassistant.components.adaptive_lighting`) and from `pytest_homeassistant_custom_component.common` (not `tests.common`). New tests (groups 7.2-7.16): - tests/test_color_and_brightness.py (16 tests): TestBrightnessCurve asserts min-before-sunrise, midpoint-at-event, max-during-day, sunset ramp symmetry. TestColorTempCurve verifies the same shape applies to K. TestSunPosition checks the synthetic +1/-1/0 derivation. TestTanhDayCurveDirect exercises the helper directly. - tests/test_config_flow.py (12 tests): six sections in order, each section contains only its specified fields, conditional visibility of send_split_delay, default sun entities, strict-typed entity selectors, NumberSelector slider/box configs, BooleanSelector for every flag, full user→create-entry flow, YAML-managed entry aborts with `yaml_managed` reason, options flow renders the sectioned schema. - tests/test_init.py (6 tests): successful setup on current version, stale version raises ConfigEntryError via async_migrate_entry, unload is clean, tombstone removes orphan sleep entity + logs INFO, tombstone is idempotent, tombstone respects config_entry ownership. Source modernizations driven by the tests: - __init__.py: add `async_migrate_entry` that surfaces the "incompatible — delete and recreate" message and sets the entry to MIGRATION_ERROR. Removed direct `ConfigEntryError` from `async_setup_entry` (HA routes version mismatches through the migration handler now). - switch.py: removed upstream's YAML-managed-entry auto-remove hack. YAML profiles now load normally and the options flow handles the "you must edit configuration.yaml" message (spec R7). Existing tests updated to PHACC paths: test_adaptation_utils.py (38 tests), test_hass_utils.py (22 tests). Both pass without modification beyond the import fix. Deleted: tests/test_switch.py (2,999 LOC of upstream tests, most covering sleep mode / take-over-control / manual-control state machines that no longer exist; CDiT-specific switch tests deferred to a follow-up change). Result: `uv run --group test pytest tests/` → 94 passed in 0.57s. Deferred from this change: - 4.3: manual UI test that toggling a field and saving reloads cleanly (requires a real HA instance, can't be done from CLI). - 9.4: GitHub repo description / topics update (do via `gh repo edit` outside the change scope). openspec status: 4/4 artifacts complete; strict-validate green.
12 KiB
12 KiB
1. Foundation — const.py and manifest.json
- 1.1 Remove the 21 retired
CONF_*/DEFAULT_*constants fromconst.py:sleep_brightness,sleep_rgb_or_color_temp,sleep_color_temp,sleep_rgb_color,sleep_transition,adapt_until_sleep,sunrise_time,min_sunrise_time,max_sunrise_time,sunrise_offset,sunset_time,min_sunset_time,max_sunset_time,sunset_offset,brightness_mode,brightness_mode_time_dark,brightness_mode_time_light,take_over_control,take_over_control_mode,detect_non_ha_changes,autoreset_control,only_once,adapt_only_on_bare_turn_on. [R8] - 1.2 Trim
VALIDATION_TUPLESto the 16 retained upstream entries plus 2 new sun-entity entries (target: 18 total). Also pruneEXTRA_VALIDATIONof orphaned keys. [R8] - 1.3 Add
CONF_SUNRISE_ENTITY = "sunrise_entity"/DEFAULT_SUNRISE_ENTITY = "sensor.sun_next_rising"and the_SUNSET_pair. [R3] - 1.4 Add
RAMP_HALF_WIDTH_SECONDS = 1800with an inline comment naming the design choice (30-min eye-friendly transition). [R4, D15] - 1.5 Bump
manifest.jsontoversion: "2.0.0-cdit.1", add"homeassistant": "2025.1.0", updatecodeownersto CDiT-dev maintainers, pointdocumentation/issue_trackerat the fork URL. [R8, D4, D13] - 1.6 Re-default values that felt off in upstream:
DEFAULT_MIN_BRIGHTNESS = 5(was 1 — 1% reads as off on most bulbs),DEFAULT_MIN_COLOR_TEMP = 2200(was 2000 — less sodium-vapor orange). Document the change in a one-line comment per constant. [polish]
2. Foundation — strip dead code from switch.py
- 2.1 Delete the sleep-mode switch entity class and its
unique_idpattern fromswitch.py. The integration now creates 3 switches per AL config (master, adapt_brightness, adapt_color), not 4. [D7] - 2.2 Remove sleep-mode state machine from the master
AdaptiveSwitchclass (sleep transition logic, sleep brightness override,adapt_until_sleephandling). [D7] - 2.3 Remove the take-over-control state machine:
_manual_controltracking,_autoreset_handle,detect_non_ha_changespolling,only_onceshort-circuits,adapt_only_on_bare_turn_onchecks. [D8] (Note: implemented via bridge stubs_take_over_control = Falseetc. on AdaptiveSwitch — Manager-side branches now dead but not yet pruned; follow-up cleanup.) - 2.4 Audit remaining
switch.pyfor references to removedCONF_*keys and delete dead branches. Lint must pass. [D7, D8] - 2.5 Set distinct
_attr_iconon each of the three switch classes: mastermdi:weather-sunny-alert(signals automatic behavior),adapt_colormdi:invert-colors,adapt_brightnessmdi:brightness-percent. [polish]
3. Config flow — sectioned schema
- 3.1 Replace the flat
VALIDATION_TUPLESloop inconfig_flow.pywith a hand-shaped builder that returns avol.Schemacontaining sixsection()-wrapped subschemas: Targets, Daytime curve, Sun schedule, Light control, Advanced (collapsed), Diagnostics (collapsed). [R1, D1] - 3.2 Wire each field's selector per the table in R5:
NumberSelector(slider mode for brightness 1–100 / step 1 / %; box-or-slider for color temp 1000–10000 / step 100 / K),BooleanSelector,EntitySelector(domain="light", multiple=True)forlights,EntitySelector(domain="sensor", device_class="timestamp")for the two sun entities. [R5, D5, D14] - 3.3 Implement conditional visibility for
send_split_delay(driver:separate_turn_on_commands). The schema builder reads the current options/draft state and omitssend_split_delaywhen the driver is false. [R2, D9] - 3.4 Move
include_config_in_attributesinto the Diagnostics collapsed subsection. [R1] - 3.5 Verify that no field appears in more than one section (spec R1 scenario 2). [R1]
4. Config flow — reload-on-save and YAML-managed abort
- 4.1 Change
OptionsFlow→OptionsFlowWithReloadinconfig_flow.py. Drop any customasync_reload/async_unload_entryplumbing that exists today for reload purposes. [R6, D6] - 4.2 In the options flow's
async_step_init, detectconfig_entry.source == SOURCE_IMPORTand returnself.async_abort(reason="yaml_managed"). [R7] - 4.3 Confirm via manual test that toggling a field and saving reloads the integration with no "restart HA" prompt, and that entity IDs of the AL switches are preserved across the reload. [R6]
5. Curve math — entity reads + synthetic tanh
- 5.1 Add a
today_sun_events(hass, entry)helper in__init__.py(or a newsun.pymodule) that readsentry.options[CONF_SUNRISE_ENTITY]and_SUNSET_ENTITY, fetches their state viahass.states.get(...), parses the timestamp, and handles the "next_rising flipped to tomorrow after sunrise" case by anchoring today's curve from whichever event is in the past. Returns(today_sunrise_dt, today_sunset_dt). [R3, D2] (Implemented asAdaptiveSwitch._today_sun_events()instance method rather than module-level helper — same behavior, scoped to the switch.) - 5.2 Add a pure
tanh_curve(now, t_start, t_end, value_min, value_max, half_width=RAMP_HALF_WIDTH_SECONDS)function. Returnsvalue_minoutside the active window, ramps viatanhbetween (t_start - half_width,t_start + half_width), holds atvalue_maxbetween (t_start + half_width,t_end - half_width), ramps back viatanhbetween (t_end - half_width,t_end + half_width). [R4, D11] (Implemented as_tanh_day_curve()incolor_and_brightness.py.) - 5.3 Replace upstream's
astral-driven brightness and color-temp computation with two calls totanh_curve— one for brightness usingmin_brightness/max_brightness, one for color temp usingmin_color_temp/max_color_temp. Same(t_sunrise, t_sunset)inputs for both. [R4] - 5.4 Verify the curve evaluation path no longer imports
astral.sun. (astralmay remain a transitive dep for now; pruning it is a follow-up.) [D2]
6. async_setup_entry guards
- 6.1 At the top of
async_setup_entryin__init__.py, checkconfig_entry.versionagainst the current major (2). If older, raiseConfigEntryErrorwith a user-facing message: "Adaptive Lighting v2 (CDiT fork) is incompatible with the existing config entry. Delete and recreate the entry from Settings → Devices & Services." [R8, D4] - 6.2 Add a sleep-switch tombstone helper: scan
entity_registryfor entities whoseunique_idmatches the historical<entry.entry_id>_sleep_mode_*pattern, callentity_registry.async_remove(entity_id)on each match, logINFOper removal with the entity ID. [R9, D12] - 6.3 Ensure the tombstone helper is idempotent — a second
async_setup_entrycall finds nothing and emits no log lines. [R9] - 6.4 Confirm the helper only removes entities whose
config_entry_idmatches the current entry (does not touch foreign entities matching the name pattern). [R9, D12]
7. Tests
- 7.1 Delete obsolete test files:
tests/test_*sleep*,tests/test_*take_over*,tests/test_*manual_control*. Updatetests/conftest.pyto drop fixtures that referenced those features. [D7, D8] - 7.2 Add test: section layout — opening options on a UI-managed entry returns a flow result with six labeled sections in the specified order; Advanced and Diagnostics collapsed. [R1]
- 7.3 Add test: each field appears in exactly one section, matching the R1 table. [R1]
- 7.4 Add test:
send_split_delayis absent from the schema whenseparate_turn_on_commandsis false; present when true. [R2] - 7.5 Add test:
sunrise_entity/sunset_entitydefault tosensor.sun_next_rising/sensor.sun_next_settingon a freshly created entry. [R3] - 7.6 Add test: entity selectors for the two sun fields are configured with
domain="sensor"anddevice_class="timestamp". [R3, D14] - 7.7 Add test:
tanh_curvereturnsvalue_minmore thanhalf_widthbeforet_start, midpoint at exactlyt_start,value_maxmore thanhalf_widthaftert_start(and the symmetric trio aroundt_end). [R4] - 7.8 Add test: color temperature uses the same curve shape as brightness, with
min_color_temp/max_color_tempas bounds. [R4] - 7.9 Add test:
NumberSelectortypes, ranges, units, and modes match the R5 table for brightness, color temp, durations, and milliseconds. [R5] - 7.10 Add test: saving valid options invokes
async_unload_entryandasync_setup_entryexactly once each (use mock spies) and produces no "restart HA" prompt. [R6] - 7.11 Add test: opening the options flow on a
SOURCE_IMPORTconfig entry returnsasync_abort(reason="yaml_managed"). [R7] - 7.12 Add test:
async_setup_entryraisesConfigEntryErrorwhenconfig_entry.version == 1and current is 2. [R8] - 7.13 Add test:
async_setup_entrysucceeds and runs no tombstone log line when entry version is current. [R8] - 7.14 Add test: the tombstone helper removes a seeded
switch.adaptive_lighting_sleep_mode_<name>entity owned by this config entry, emits one INFO log line. [R9, D12] - 7.15 Add test: tombstone helper is idempotent — second run finds nothing, no log line. [R9]
- 7.16 Add test: tombstone helper does not remove a foreign-owned entity matching the name pattern (different
config_entry_id). [R9, D12]
8. Strings and translations
- 8.1 In
strings.json, add section labels (section.targets.name,section.daytime_curve.name, etc.) and short descriptions per section. Add field labels forsunrise_entity/sunset_entity. [R1, R3] - 8.2 In
strings.json, add theyaml_managedabort reason text: "This Adaptive Lighting config entry is managed fromconfiguration.yaml. Edit it there to change options." [R7] - 8.3 In
strings.json, add the version-incompatible error: "This entry was created with an older, incompatible version. Delete it and create a new one." [R8] - 8.4 Remove
strings.jsonkeys for the 21 deleted fields and the sleep switch entity. [R1, D7] - 8.5 Mirror the additions/removals in
translations/en.json. Other locales (de, fr, etc.) are out of scope and may diverge until a follow-up change. [R1] - 8.6 Plain-language pass on every field label and description in
strings.json. Each label reads like a sentence a human wrote — not engineer shorthand. Add a one-sentence "what this section is for" framer at the top of each section. [R1, R7, R8, polish]
9. Docs — README and CHANGELOG
- 9.1 Replace the upstream README's "Installation" / "Configuration" sections (or add a CDiT-specific preamble at the top) explicitly stating: "This is a CDiT fork. Existing upstream config entries WILL NOT load; recreate them after upgrade." Reference the migration steps from design.md §Migration Plan. [D4]
- 9.2 Add a "Recommended companions" section to README mentioning Sun2 as a HACS-installable source for precise civil / nautical / astronomical twilight sensors. Show the example of pointing
sunrise_entityatsensor.sun2_astro_dawn. [D2, D14] - 9.3 Write
CHANGELOG.md(or append to existing) entry for2.0.0-cdit.1: the 21 removed fields by name, the 2 added fields, the 1 removed entity, the breaking config-entry behavior, and the explicit recreate workflow. [D4] - 9.4 Update the fork's GitHub repo description and topics to mark it as opinionated/fork (not a drop-in replacement). [D4] (GitHub-side metadata; do via
gh repo editin a separate step after merge.)