feat: mirror create-side validation guards onto update path (closes #1292)#1304
feat: mirror create-side validation guards onto update path (closes #1292)#1304Patch76 wants to merge 3 commits into
Conversation
Issue homeassistant-ai#1292 (parity with homeassistant-ai#1150): two input-side guards that fire on the create branch of ha_config_set_helper were missing from the update branch — input_select 'initial must be in options', and input_datetime 'at least one of has_date / has_time must be True'. A caller updating options (or only the initial value) could leave the helper with an initial that's not in its options list — Home Assistant either accepts the broken state silently or replies with the same generic error message the homeassistant-ai#1150 create-side guards were added to pre-empt. Same for input_datetime: a merge that resolves both has_date and has_time to False would write a broken-entity payload. Extract the inline create-branch checks into two module-level validators (matching the established pattern at L869-998: _validate_numeric_range, _validate_input_select_options, _validate_schedule_days) and call them from both branches: - _validate_initial_in_options(options, initial) — accepts the resolved values from either branch, handles initial=None as the unset case - _validate_datetime_has_date_or_time(has_date, has_time) — flags only the explicit (False, False) combination the issue targets Add unit-test coverage that didn't exist for the homeassistant-ai#1150 create-side guards (4 tests) and mirror it on the update path (7 tests), spanning all three merge-resolution cases for input_select (caller-supplied, existing-fall-through, mixed) and both happy-path + invariant-violating update for input_datetime. Closes homeassistant-ai#1292 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses a parity gap in input validation for 'input_select' and 'input_datetime' entities. Previously, certain validation constraints were only enforced during entity creation, allowing invalid configurations to be written during updates. By centralizing these checks into shared helper functions, the PR ensures that both create and update paths enforce the same data integrity rules, preventing the creation of broken entity states. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request refactors validation logic for input_select and input_datetime helpers into shared utility functions, ensuring consistent validation across both creation and update paths (addressing issue #1292). Specifically, it ensures initial values exist within the provided options and that input_datetime entities define at least a date or a time. Comprehensive unit tests were added to verify these guards. Feedback was provided to enhance the input_datetime error response by including relevant context and actionable suggestions in alignment with the repository's style guide.
Adopts Gemini medium-priority finding: the new validator's error response was missing both the context values and a suggestions array, breaking sibling consistency with _validate_initial_in_options (same PR) and the wider _validate_* family in this module. - context now includes has_date / has_time so the failing payload is visible in the structured error. - suggestions array mirrors the active-instruction style of sibling validators (no documentation link — _validate_initial_in_options and _validate_input_select_options don't ship one either). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request refactors validation logic for input_select and input_datetime helpers into shared functions, extending validation to the update path to prevent broken entity configurations. It also adds comprehensive unit tests for these scenarios. Review feedback suggests adding defensive type checks for the options parameter and refining error message suggestions to be more accurate for update operations.
…tion wording Adopts both Gemini medium findings on the second review wave: - guard: add isinstance(options, list) check before the membership test. Both current callers feed a list (create at L2460, update at L3091), but the guard mirrors the defensive shape check in _validate_input_select_options and prevents a future non-list caller from raising a confusing TypeError on 'initial not in options'. - suggestion wording: 'Or omit initial so the entity starts unset' is accurate for create but misleading on update where the merge logic at L3079-3084 preserves the existing value when initial is None. Reworded to 'Or omit initial to use the default or existing value' so the error covers both paths the validator is reachable from. No test changes — the defensive guard is a no-op for list inputs and no test asserts on the specific suggestion text. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request refactors validation logic for input_select and input_datetime helpers into shared utility functions and extends these guards to the update path in ha_config_set_helper to ensure parity with creation logic. New unit tests provide coverage for these validation scenarios. Feedback suggests improving the error response suggestions in _validate_initial_in_options to avoid misleading users during updates where configuration merging occurs, and to include a documentation link for consistency with sibling tools.
Implementation SummaryChoices Made:
Problems Encountered:
Suggested Improvements (post-merge):
|
kingpanther13
left a comment
There was a problem hiding this comment.
Clean refactor — extract-and-share is faithful, merge ordering puts resolution before validation, ToolError propagation is intact through the outer guard at L3387. A few things to address before merge.
Requested changes
1. Strip issue-number refs from docstrings/comments. Per AGENTS.md "comments shouldn't reference the current task/fix/issue — belongs in the PR description." The #1150 / #1292 refs in _validate_initial_in_options (L867-885) and _validate_datetime_has_date_or_time (L887-898), in the call-site comments at L2459-2461 / L3087-3091 / L3187-3191, and in the test class docstrings at L181-187 / L305-311 will read as rot once the migration framing fades. The durable WHY is the parity invariant — keep that, drop the issue numbers.
2. Pick one shape strategy for options. _validate_initial_in_options declares options: list[Any] but the body still does isinstance(options, list). Sibling _validate_input_select_options at L869-908 uses options: Any + an internal shape check — match it, or trust the type and drop the runtime guard. The current "typed-narrowly but defended anyway" reads as confused intent.
3. The isinstance(options, list) guard is unreachable. Both call sites feed lists (caller path goes through parse_string_list_param; existing path defaults to []). If you keep the guard as defense-in-depth, add a direct unit test pinning the contract. Otherwise drop it — unreachable guards drift into "why is this here?" over time.
While you're in there
- Missing test edges.
initial=""againstoptions=["A","B"](empty string treated as set, should reject),options=[]on update with anyinitial, and a happy-path update where caller passeshas_date=None/has_time=Noneagainst an existing(False, True)config. Cheap to add and locks the contract. - Hardcoded
"input_select"in error context (_validate_initial_in_optionsL887). Fine today — both callers are input_select branches — but a future reuse on another helper type would emit misleading context. Ahelper_typeparam costs one line. - Collapse
_wire_existing_configinto_wire_default_ws. It's a thin wrapper that differs only by passingexisting_config=. Adding a defaultexisting_config=Noneparam to the original is one helper instead of two. - PR description should note the create-branch suggestion text change. "Or omit
initialso the entity starts unset" → "Or omitinitialto use the default or existing value." Intentional generalization for shared use, but it's a user-visible message shift on the create path — worth surfacing so it doesn't get lost.
What does this PR do?
Closes the parity gap that issue #1292 surfaces: two input-side validation guards that fire on the create branch of
ha_config_set_helperare absent from the update branch.input_selectinitial∈optionssrc/ha_mcp/tools/tools_config_helpers.py:L2401-2422on master)input_datetimehas_date/has_timeisTrueL2481-2488on master)A caller hitting either today reaches the same broken-entity state with
success: truethat #1150 closed for the create path, or surfaces the generic HA error message the create-side guard exists to pre-empt.Approach — extract-and-share, matching the sibling pattern at
tools_config_helpers.py:L869-998(_validate_numeric_range,_validate_input_select_options,_validate_schedule_days):_validate_initial_in_options(options, initial)— accepts the resolved values from either branch; treatsinitial=Noneas the unset case so a merge-fall-through that leavesinitialunset doesn't false-positive._validate_datetime_has_date_or_time(has_date, has_time)— flags only the explicit(False, False)resolved-after-merge combo the issue targets;Nonepasses through (not constrained).Both validators are called from the create branch (replacing the inline blocks) and from the update branch after the merge step that resolves the final
(options, initial)and(has_date, has_time)pairs.Type of change
Testing
uv run pytest)uv run ruff check)Local runs (Alpine, Python 3.13 musl):
tests/src/unit/test_helper_input_validation.py): 11 added — 4 closing the previously-untested coverage gap for the [BUG] ha_config_set_helper: 19 bugs found in audit (silent drops, destructive UPDATE, broken multi-step flows, phantom-ID acceptance, etc.) #1150 create-side guards (the original PR didn't ship unit tests for them), 7 covering the new update-side parity:TestInputSelectInitialInOptions(6 tests): create-side rejection + happy path; update-side rejection in all three merge-resolution combinations (caller-new-options + caller-new-initial; caller-only-options + existing-initial-falls-out; caller-only-initial + existing-options); update happy path.TestInputDatetimeHasDateOrTime(5 tests): create-side(False, False)rejection + valid create; update-side rejection when caller disables both components or the only-remaining one; update happy path.tools_config_helpers.pyFuture improvements
tools_config_helpers.py(e.g.counter,timer,input_textranges). The numeric-range guard already runs on update via_validate_numeric_range; the remaining per-type fields would benefit from the same audit. Out-of-scope here to keep the PR narrowly aligned with issue [FEATURE] Mirror create-side validation guards to update path in ha_config_set_helper (parity with #1150) #1292.Checklist