Compare commits

..

1 Commits

Author SHA1 Message Date
Bram Kragten f270b52521 Update frontend to 20260429.4 2026-06-03 12:54:58 +02:00
14 changed files with 51 additions and 901 deletions
+33 -2
View File
@@ -8,8 +8,39 @@ description: Reviews GitHub pull requests and provides feedback comments. This i
## Follow these steps:
1. Use 'gh pr view' to get the PR details and description.
2. Use 'gh pr diff' to see all the changes in the PR.
3. Review the changes following the `review` skill. It is VERY IMPORTANT to follow the `review` skill instructions.
4. Check if all existing review comments have been addressed.
3. Analyze the code changes for:
- Code quality and style consistency
- Potential bugs or issues
- Performance implications
- Security concerns
- Test coverage
- Documentation updates if needed
4. Ensure any existing review comments have been addressed.
5. Generate constructive review comments in the CONSOLE. DO NOT POST TO GITHUB YOURSELF.
## Verification:
- After the review, run parallel subagents for each finding to double check it.
- Spawn up to a maximum of 10 parallel subagents at a time.
- Gather the results from the subagents and summarize them in the final review comments.
## IMPORTANT:
- Just review. DO NOT make any changes
- Be constructive and specific in your comments
- Suggest improvements where appropriate
- Only provide review feedback in the CONSOLE. DO NOT ACT ON GITHUB.
- No need to run tests or linters, just review the code changes.
- No need to highlight things that are already good.
## Output format:
- List specific comments for each file/line that needs attention.
- In the end, summarize with an overall assessment (approve, request changes, or comment) and bullet point list of changes suggested, if any.
- Example output:
```
Overall assessment: request changes.
- [CRITICAL] sensor.py:143 - Memory leak
- [PROBLEM] data_processing.py:87 - Inefficient algorithm
- [SUGGESTION] test_init.py:45 - Improve x variable name
```
- Make sure to include the file and line number when possible in the bullet points.
-38
View File
@@ -1,38 +0,0 @@
---
name: review
description: Reviews code changes and provides constructive feedback. Should be used when a review is requested to provide a consistent review behavior and output format. This skill can be used for code reviews in general, not just for GitHub pull requests.
---
# Review Code Changes
## Analyze the code changes for:
- Code quality and style consistency
- Potential bugs or issues
- Performance implications
- Security concerns
- Test coverage
- Documentation updates if needed
## Verification:
- After the review, run parallel subagents for each finding to double-check it.
- Spawn up to a maximum of 10 parallel subagents at a time.
- Gather the results from the subagents and summarize them in the final review comments.
## IMPORTANT:
- Just review. DO NOT make any changes.
- Be constructive and specific in your comments.
- Suggest improvements where appropriate.
- No need to run tests or linters, just review the code changes.
- No need to highlight things that are already good.
## Output format:
- List specific comments for each file/line that needs attention.
- In the end, summarize with an overall assessment (approve, request changes, or comment) and bullet point list of changes suggested, if any.
- Example output:
```
Overall assessment: request changes.
- [CRITICAL] sensor.py:143 - Memory leak
- [PROBLEM] data_processing.py:87 - Inefficient algorithm
- [SUGGESTION] test_init.py:45 - Improve x variable name
```
- Make sure to include the file and line number when possible in the bullet points.
+5 -19
View File
@@ -2,18 +2,12 @@
import avea
from homeassistant.components.bluetooth import (
BluetoothReachabilityIntent,
async_address_reachability_diagnostics,
async_ble_device_from_address,
)
from homeassistant.components.bluetooth import async_ble_device_from_address
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_ADDRESS, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from .const import DOMAIN
type AveaConfigEntry = ConfigEntry[avea.Bulb]
PLATFORMS: list[Platform] = [Platform.LIGHT]
@@ -21,20 +15,12 @@ PLATFORMS: list[Platform] = [Platform.LIGHT]
async def async_setup_entry(hass: HomeAssistant, entry: AveaConfigEntry) -> bool:
"""Set up Avea from a config entry."""
address = entry.data[CONF_ADDRESS]
ble_device = async_ble_device_from_address(hass, address, connectable=True)
ble_device = async_ble_device_from_address(
hass, entry.data[CONF_ADDRESS], connectable=True
)
if not ble_device:
raise ConfigEntryNotReady(
translation_domain=DOMAIN,
translation_key="device_not_found",
translation_placeholders={
"address": address,
"reason": async_address_reachability_diagnostics(
hass,
address.upper(),
BluetoothReachabilityIntent.CONNECTION,
),
},
f"Could not find Avea device with address {entry.data[CONF_ADDRESS]}"
)
entry.runtime_data = avea.Bulb(ble_device)
@@ -22,11 +22,6 @@
}
}
},
"exceptions": {
"device_not_found": {
"message": "Could not find Avea device with address {address}: {reason}"
}
},
"issues": {
"deprecated_yaml": {
"description": "[%key:component::homeassistant::issues::deprecated_yaml::description%]",
@@ -21,5 +21,5 @@
"integration_type": "system",
"preview_features": { "winter_mode": {} },
"quality_scale": "internal",
"requirements": ["home-assistant-frontend==20260527.4"]
"requirements": ["home-assistant-frontend==20260429.4"]
}
@@ -1027,53 +1027,14 @@ async def handle_test_condition(
hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
) -> None:
"""Handle test condition command."""
# Validating and instantiating the condition can fail on bad user input.
# Handle those errors here so they are reported to the client without being
# logged as unexpected errors by the default websocket error handler.
# Do static + dynamic validation of the condition
config = await async_validate_condition_config(hass, msg["condition"])
# Test the condition
condition = await async_condition_from_config(hass, config)
try:
# Do static + dynamic validation of the condition
config = await async_validate_condition_config(hass, msg["condition"])
condition = await async_condition_from_config(hass, config)
except vol.Invalid as err:
connection.send_error(msg["id"], const.ERR_INVALID_FORMAT, str(err))
return
except HomeAssistantError as err:
connection.send_error(
msg["id"],
const.ERR_HOME_ASSISTANT_ERROR,
str(err),
translation_domain=err.translation_domain,
translation_key=err.translation_key,
translation_placeholders=err.translation_placeholders,
connection.send_result(
msg["id"], {"result": condition.async_check(variables=msg.get("variables"))}
)
return
# Template errors (e.g. undefined variables) are recorded in the trace
# instead of being logged. Capture the trace and forward them to the client
# alongside the result.
condition_trace = trace.trace_get()
try:
with trace.record_template_errors():
check_result = condition.async_check(variables=msg.get("variables"))
except HomeAssistantError as err:
connection.send_error(
msg["id"],
const.ERR_HOME_ASSISTANT_ERROR,
str(err),
translation_domain=err.translation_domain,
translation_key=err.translation_key,
translation_placeholders=err.translation_placeholders,
)
else:
result: dict[str, Any] = {"result": check_result}
if template_errors := [
template_error
for elements in condition_trace.values()
for element in elements
for template_error in element.template_errors
]:
result["template_errors"] = template_errors
connection.send_result(msg["id"], result)
finally:
condition.async_unload()
+1 -1
View File
@@ -39,7 +39,7 @@ habluetooth==6.8.1
hass-nabucasa==2.2.0
hassil==3.5.0
home-assistant-bluetooth==2.0.0
home-assistant-frontend==20260527.4
home-assistant-frontend==20260429.4
home-assistant-intents==2026.6.1
httpx==0.28.1
ifaddr==0.2.0
-242
View File
@@ -99,31 +99,11 @@ Every check has a code following the
| `W7407` | [`home-assistant-config-flow-polling-field`](#w7407-home-assistant-config-flow-polling-field) | Config flow should not include polling interval fields |
| `W7408` | [`home-assistant-config-flow-name-field`](#w7408-home-assistant-config-flow-name-field) | Config flow should not include name fields |
| `R7402` | [`home-assistant-unused-test-fixture-argument`](#r7402-home-assistant-unused-test-fixture-argument) | Unused test function argument should use `@pytest.mark.usefixtures` |
| `C7415` | [`home-assistant-domain-argument`](#c7415-home-assistant-domain-argument) | Domain argument in tests should be a domain constant or variable |
| `W7418` | [`home-assistant-tests-direct-async-setup-entry`](#w7418-home-assistant-tests-direct-async-setup-entry) | Tests should not call an integration's `async_setup_entry` directly |
| `W7420` | [`home-assistant-tests-direct-platform-async-setup-entry`](#w7420-home-assistant-tests-direct-platform-async-setup-entry) | Tests should not call a platform's `async_setup_entry` directly |
| `W7421` | [`home-assistant-tests-direct-async-migrate-entry`](#w7421-home-assistant-tests-direct-async-migrate-entry) | Tests should not call an integration's `async_migrate_entry` directly |
| `W7422` | [`home-assistant-tests-direct-async-setup`](#w7422-home-assistant-tests-direct-async-setup) | Tests should not call an integration's `async_setup` directly |
| `C7414` | [`home-assistant-enforce-utcnow`](#c7414-home-assistant-enforce-utcnow) | Use `homeassistant.util.dt.utcnow` instead of `datetime.now(UTC)` |
| `C7412` | [`home-assistant-entity-description-redundant-default`](#c7412-home-assistant-entity-description-redundant-default) | Setting an EntityDescription field to its default value is redundant |
| `C7413` | [`home-assistant-duplicate-const`](#c7413-home-assistant-duplicate-const) | Constant duplicates one in `homeassistant.const` with the same value |
| `E7405` | [`home-assistant-action-swallowed-exception`](#e7405-home-assistant-action-swallowed-exception) | Action handler must not swallow exceptions |
| `W7414` | [`home-assistant-service-registered-in-setup-entry`](#w7414-home-assistant-service-registered-in-setup-entry) | Services should be registered in `async_setup`, not `async_setup_entry` |
| `W7417` | [`home-assistant-exception-not-translated`](#w7417-home-assistant-exception-not-translated) | `HomeAssistantError` should use `translation_key`/`translation_domain` |
| `W7419` | [`home-assistant-exception-message-with-translation`](#w7419-home-assistant-exception-message-with-translation) | Don't pass a positional message when `translation_key` is set |
| `E7406` | [`home-assistant-exception-translation-key-missing`](#e7406-home-assistant-exception-translation-key-missing) | Translation key not found in `strings.json` exceptions section |
| `E7408` | [`home-assistant-exception-translation-key-domain-mismatch`](#e7408-home-assistant-exception-translation-key-domain-mismatch) | Only one of `translation_key` / `translation_domain` is set |
| `E7418` | [`home-assistant-exception-placeholder-mismatch`](#e7418-home-assistant-exception-placeholder-mismatch) | Translation placeholders in code don't match `strings.json` |
| `E7409` | [`home-assistant-mdi-icon-not-found`](#e7409-home-assistant-mdi-icon-not-found) | MDI icon string does not exist in the Material Design Icons set |
| `E7410` | [`home-assistant-mdi-icon-json-not-found`](#e7410-home-assistant-mdi-icon-json-not-found) | MDI icon in `icons.json` does not exist in the Material Design Icons set |
| `R7403` | [`home-assistant-tests-redundant-usefixtures`](#r7403-home-assistant-tests-redundant-usefixtures) | `@pytest.mark.usefixtures` redundant when `pytestmark` already applies it |
| `W7409` | [`home-assistant-test-non-deterministic`](#w7409-home-assistant-test-non-deterministic) | Test contains `if`/`match` creating non-deterministic execution |
| `W7410` | [`home-assistant-missing-reauthentication-flow`](#w7410-home-assistant-missing-reauthentication-flow) | Config flow should implement `async_step_reauth` |
| `W7411` | [`home-assistant-missing-parallel-updates`](#w7411-home-assistant-missing-parallel-updates) | Platform module should define `PARALLEL_UPDATES` |
| `W7412` | [`home-assistant-missing-diagnostics`](#w7412-home-assistant-missing-diagnostics) | Integration diagnostics module should implement a diagnostics function |
| `W7413` | [`home-assistant-missing-config-entry-unloading`](#w7413-home-assistant-missing-config-entry-unloading) | Integration should implement `async_unload_entry` |
| `W7415` | [`home-assistant-sequential-executor-jobs`](#w7415-home-assistant-sequential-executor-jobs) | Sequential `async_add_executor_job` calls should be grouped |
| `W7416` | [`home-assistant-missing-has-entity-name`](#w7416-home-assistant-missing-has-entity-name) | Entity class should set `_attr_has_entity_name = True` |
## `home_assistant_logger` checker
@@ -366,27 +346,6 @@ only needed for its side effects.
This rule only applies to `test_*` functions, not to fixture functions.
## `home_assistant_domain_constant` checker
Encourages using `DOMAIN` constants (or variables) when passing a domain to common test helpers.
String literals are allowed for cases where the constant is not imported.
Only runs on test modules.
### `C7415`: `home-assistant-domain-argument`
The domain (or handler) argument to test helpers such as
`async_setup_component`, `async_mock_service`, `MockConfigEntry`,
`hass.services.async_call`, `hass.services.call`, and
`hass.config_entries.flow.async_init` should use a domain constant or variable when available.
The following are accepted:
* a `DOMAIN`/`domain` attribute or one ending in `_DOMAIN`/`_domain`
(e.g. `sensor.DOMAIN`),
* a `DOMAIN`/`domain` name or one ending in `_DOMAIN`/`_domain`,
* a string literal (for cases where the constant is not imported),
* a subscript expression (e.g. `data["key"]`).
## `home_assistant_tests_direct_async_setup_entry` checker
Detects tests that call an integration's `async_setup_entry` directly.
@@ -454,204 +413,3 @@ The helper is implemented as
`functools.partial(datetime.datetime.now, UTC)` and avoids the global
lookup of `UTC` on every call, while keeping the codebase consistent in
how the current UTC time is obtained.
## `home_assistant_entity_description_defaults` checker
Detects fields in `EntityDescription` (and subclasses) that are explicitly
set to their default value.
### `C7412`: `home-assistant-entity-description-redundant-default`
An EntityDescription field is set equal to a default already declared
anywhere in the class hierarchy; the assignment can be removed. Only the
literal defaults `None`, `True`, and `False` are checked; other default
values are not flagged.
## `home_assistant_duplicate_const` checker
Detects constants in integration modules that duplicate one already exported
from `homeassistant.const` with the same value.
### `C7413`: `home-assistant-duplicate-const`
Import the constant from `homeassistant.const` instead of redefining it.
## `home_assistant_actions_swallowed_exceptions` checker
Detects action handlers that catch exceptions without re-raising. Swallowed
exceptions are not surfaced to the user.
### `E7405`: `home-assistant-action-swallowed-exception`
Action handlers must re-raise so the user is notified of the failure.
The checker detects empty `except` blocks, blocks that only log,
`contextlib.suppress(...)`, and equivalent patterns on decorators. It does
not validate the *type* of exception being raised; a separate rule covers
that.
## `home_assistant_actions_service_registration` checker
Detects services registered inside `async_setup_entry` rather than
`async_setup`.
### `W7414`: `home-assistant-service-registered-in-setup-entry`
Services should be registered in `async_setup` so they are available for
automation validation even when no config entry is loaded. Registrations
inside helper functions that are called from `async_setup_entry` are
caught too.
See the [action-setup quality scale rule](https://developers.home-assistant.io/docs/core/integration-quality-scale/rules/action-setup).
## `home_assistant_exception_translations` checker
Ensures `HomeAssistantError` and its subclasses use the translation system
(`translation_domain`, `translation_key`) instead of hardcoded English
strings. Also verifies that referenced translation keys exist in the
integration's `strings.json` and that placeholders match.
### `W7417`: `home-assistant-exception-not-translated`
A `HomeAssistantError` subclass is raised with a hardcoded message; use
`translation_domain` and `translation_key` instead. Quality-scale-gated.
### `W7419`: `home-assistant-exception-message-with-translation`
Don't pass a positional message argument when `translation_key` is also set;
the translation system supplies the message.
### `E7406`: `home-assistant-exception-translation-key-missing`
The translation key referenced from code is missing from `strings.json`
under the `exceptions` section.
### `E7408`: `home-assistant-exception-translation-key-domain-mismatch`
Both `translation_key` and `translation_domain` must be set together; only
one of the two was provided.
### `E7418`: `home-assistant-exception-placeholder-mismatch`
The placeholders passed in code (e.g. `translation_placeholders={...}`)
don't match the `{placeholder}` slots in the `strings.json` message.
## `home_assistant_mdi_icons` checker
Validates that `mdi:` icon references in code and `icons.json` refer to
icons that actually exist in the Material Design Icons set.
### `E7409`: `home-assistant-mdi-icon-not-found`
MDI icon reference in Python code does not exist in the Material Design
Icons set.
### `E7410`: `home-assistant-mdi-icon-json-not-found`
MDI icon reference in `icons.json` does not exist in the Material Design
Icons set.
## `home_assistant_tests_redundant_usefixtures` checker
Detects `@pytest.mark.usefixtures(...)` decorators that duplicate a fixture
already applied module-wide, either through a module-level `pytestmark`
or via `autouse=True` on a fixture defined in a parent `conftest.py`.
### `R7403`: `home-assistant-tests-redundant-usefixtures`
Drop the redundant `@pytest.mark.usefixtures` decorator; the fixture is
already applied to every test in the module.
## `home_assistant_test_determinism` checker
`if` and `match` statements inside test functions create non-deterministic
execution paths: some branches may never run, silently hiding failures.
### `W7409`: `home-assistant-test-non-deterministic`
Test function contains an `if` or `match` statement. Use
`@pytest.mark.parametrize` to cover cases explicitly, or split into separate
test functions. `if` statements have several exemptions: guard clauses
(`return`/`raise`/`pytest.skip`/`pytest.xfail`/`pytest.fail`),
conditions that reference a function parameter, and branches that contain
no `assert`. `match` statements have no exemptions.
## `home_assistant_reauthentication_flow` checker
Quality-scale-gated checker for the
[`reauthentication-flow`](https://developers.home-assistant.io/docs/core/integration-quality-scale/rules/reauthentication-flow)
Silver rule. Fires only when the integration claims the rule as `done`.
### `W7410`: `home-assistant-missing-reauthentication-flow`
Integration's `config_flow.py` should implement `async_step_reauth`.
## `home_assistant_parallel_updates` checker
Quality-scale-gated checker for the
[`parallel-updates`](https://developers.home-assistant.io/docs/core/integration-quality-scale/rules/parallel-updates)
Silver rule. Fires only when the integration claims the rule as `done`.
### `W7411`: `home-assistant-missing-parallel-updates`
Platform module should define a module-level `PARALLEL_UPDATES` constant.
## `home_assistant_diagnostics` checker
Quality-scale-gated checker for the
[`diagnostics`](https://developers.home-assistant.io/docs/core/integration-quality-scale/rules/diagnostics)
Gold rule. Fires only when the integration claims the rule as `done`.
### `W7412`: `home-assistant-missing-diagnostics`
Integration's `diagnostics.py` should implement
`async_get_config_entry_diagnostics` or `async_get_device_diagnostics`.
## `home_assistant_config_entry_unloading` checker
Quality-scale-gated checker for the
[`config-entry-unloading`](https://developers.home-assistant.io/docs/core/integration-quality-scale/rules/config-entry-unloading)
Silver rule. Fires only when the integration claims the rule as `done`.
### `W7413`: `home-assistant-missing-config-entry-unloading`
Integration's `__init__.py` should implement `async_unload_entry`.
## `home_assistant_sequential_executor_jobs` checker
Detects consecutive `async_add_executor_job` calls in integration modules
that could be grouped into a single executor job.
### `W7415`: `home-assistant-sequential-executor-jobs`
Two or more `async_add_executor_job` calls appearing as consecutive
statements (uninterrupted by control flow such as `if`/`try`/`with`/`for`)
should be combined into a single executor job that performs all the work,
avoiding unnecessary context switches back to the event loop between
blocking calls. The rule applies to integration modules only.
## `home_assistant_has_entity_name` checker
Quality-scale-gated checker for the
[`has-entity-name`](https://developers.home-assistant.io/docs/core/integration-quality-scale/rules/has-entity-name)
Bronze rule. Fires only when the integration claims the rule as `done`.
### `W7416`: `home-assistant-missing-has-entity-name`
Entity class should statically guarantee `_attr_has_entity_name = True`:
either set at class level, set unconditionally at the top of a method, or
supplied by an `entity_description` whose class sets `has_entity_name = True`.
Conditional patterns are rejected.
@@ -1,149 +0,0 @@
"""Plugin to encourage correct use of DOMAIN constants in tests."""
from astroid import nodes
from pylint.checkers import BaseChecker
from pylint.lint import PyLinter
from pylint_home_assistant.helpers.module_info import is_test_module
_FUNCTION_CHECKS: list[tuple[str, int | None, str]] = [
("async_setup_component", 1, "domain"),
("async_mock_service", 1, "domain"),
("MockConfigEntry", None, "domain"),
]
_METHOD_CHECKS: list[tuple[str, str, int | None, str]] = [
("hass.services", "async_call", 0, "domain"),
("hass.services", "call", 0, "domain"),
("hass.config_entries.flow", "async_init", 0, "handler"),
]
_DOMAIN_CONSTANTS: set[str] = {"DOMAIN", "domain"}
_DOMAIN_SUFFIXES: tuple[str, ...] = ("_DOMAIN", "_domain")
def _check_call_node_domain_arguments(node: nodes.Call) -> nodes.NodeNG | None:
"""Ensure the call node arguments are valid domain constant or variable.
Return None if the argument node is valid, or the argument node if it is invalid.
"""
match node.func:
case nodes.Attribute():
for (
method_source,
method_name,
arg_position,
kwarg_name,
) in _METHOD_CHECKS:
if (
node.func.attrname == method_name
and node.func.expr.as_string() == method_source
):
return _check_call_node_domain_argument(
node, arg_position=arg_position, kwarg_name=kwarg_name
)
case nodes.Name():
for func_name, arg_position, kwarg_name in _FUNCTION_CHECKS:
if node.func.name == func_name:
return _check_call_node_domain_argument(
node, arg_position=arg_position, kwarg_name=kwarg_name
)
return None
def _check_call_node_domain_argument(
call_node: nodes.Call,
*,
arg_position: int | None,
kwarg_name: str,
) -> nodes.NodeNG | None:
"""Ensure the argument node is a domain constant or variable.
Return None if the argument node is valid, or the argument node if it is invalid.
"""
if arg_position is not None and len(call_node.args) > arg_position:
argument_node = call_node.args[arg_position]
else:
argument_node = next(
iter(
keyword.value
for keyword in call_node.keywords
if keyword.arg == kwarg_name
),
None,
)
if argument_node and not _check_domain_argument(argument_node):
return argument_node
return None
def _check_domain_argument(arg_node: nodes.NodeNG) -> bool:
"""Ensure the argument node is a domain constant or variable.
We allow:
- x.DOMAIN/x.domain attribute (including *_DOMAIN/*_domain)
- DOMAIN/domain name (including *_DOMAIN/*_domain)
- string literals
- subscript expressions (e.g. data["key"])
Return True if the argument is valid, False otherwise.
"""
match arg_node:
case nodes.Attribute():
if (
attrname := arg_node.attrname
) in _DOMAIN_CONSTANTS or attrname.endswith(_DOMAIN_SUFFIXES):
return True
case nodes.Const():
if isinstance(arg_node.value, str):
return True
case nodes.Name():
if (node_name := arg_node.name) in _DOMAIN_CONSTANTS or node_name.endswith(
_DOMAIN_SUFFIXES
):
return True
case nodes.Subscript():
# Ignore subscripts like dict["key"]
return True
return False
class DomainConstantChecker(BaseChecker):
"""Checker for correct use of DOMAIN constants in tests."""
name = "home_assistant_domain_constant"
priority = -1
msgs = {
"C7415": (
"Argument %s should be a domain constant or variable in %s",
"home-assistant-domain-argument",
"Used when argument should be a domain constant/variable.",
),
}
options = ()
_in_test_module: bool
def visit_module(self, node: nodes.Module) -> None:
"""Visit Module node."""
self._in_test_module = is_test_module(node.name)
def visit_call(self, node: nodes.Call) -> None:
"""Visit Call node."""
if not self._in_test_module:
return
if invalid_arg_node := _check_call_node_domain_arguments(node):
self.add_message(
"home-assistant-domain-argument",
node=invalid_arg_node,
args=(invalid_arg_node.as_string(), node.func.as_string()),
)
def register(linter: PyLinter) -> None:
"""Register the checker."""
linter.register_checker(DomainConstantChecker(linter))
+1 -1
View File
@@ -5,7 +5,7 @@ To update, run python3 -m script.hassfest
from typing import Final
FRONTEND_VERSION: Final[str] = "20260527.4"
FRONTEND_VERSION: Final[str] = "20260429.4"
MDI_ICONS: Final[set[str]] = {
"ab-testing",
+1 -1
View File
@@ -1269,7 +1269,7 @@ hole==0.9.0
holidays==0.97
# homeassistant.components.frontend
home-assistant-frontend==20260527.4
home-assistant-frontend==20260429.4
# homeassistant.components.conversation
home-assistant-intents==2026.6.1
+3 -18
View File
@@ -2,11 +2,8 @@
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from homeassistant.components.avea.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_ADDRESS
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
from homeassistant.helpers import issue_registry as ir
from homeassistant.setup import async_setup_component
@@ -53,31 +50,19 @@ async def _setup_yaml_import(hass: HomeAssistant, bulbs: list[MagicMock]) -> Non
async def test_setup_entry_retries_when_ble_device_is_missing(
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test setup retries when the Bluetooth device is unavailable."""
mock_config_entry.add_to_hass(hass)
with (
patch(
"homeassistant.components.avea.async_ble_device_from_address",
return_value=None,
),
patch(
"homeassistant.components.avea.async_address_reachability_diagnostics",
return_value="mock reachability reason",
),
with patch(
"homeassistant.components.avea.async_ble_device_from_address",
return_value=None,
):
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
assert (
"Could not find Avea device with address "
f"{mock_config_entry.data[CONF_ADDRESS]}: mock reachability reason"
in caplog.text
)
async def test_yaml_import_creates_entries_for_discovered_bulbs(
@@ -2821,131 +2821,6 @@ async def test_test_condition(
assert msg["result"]["result"] is False
@pytest.mark.parametrize(
("value_template", "expected_template_errors"),
[
("{{ no_such_variable }}", ["'no_such_variable' is undefined"]),
# A single render emitting multiple errors forwards all of them
("{{ foo }}{{ bar }}", ["'foo' is undefined", "'bar' is undefined"]),
],
)
async def test_test_condition_template_error(
hass: HomeAssistant,
websocket_client: MockHAClientWebSocket,
caplog: pytest.LogCaptureFixture,
value_template: str,
expected_template_errors: list[str],
) -> None:
"""Test template errors are forwarded in the result without being logged."""
caplog.set_level(logging.WARNING)
await websocket_client.send_json_auto_id(
{
"type": "test_condition",
"condition": {"condition": "template", "value_template": value_template},
}
)
msg = await websocket_client.receive_json()
assert msg["type"] == const.TYPE_RESULT
assert msg["success"]
assert msg["result"] == {
"result": False,
"template_errors": expected_template_errors,
}
assert "Template variable" not in caplog.text
@pytest.mark.parametrize(
("condition", "expected_error"),
[
# Missing mandatory config, raised by async_validate_condition_config
(
{"condition": "sun"},
{
"code": "invalid_format",
"message": (
"must contain at least one of before, after. for dictionary value "
"@ data['options']"
),
},
),
# Failing enabled template, raised by async_condition_from_config
(
{
"condition": "template",
"value_template": "{{ true }}",
"enabled": "{{ 1 / 0 }}",
},
{
"code": "home_assistant_error",
"message": (
"Error rendering condition enabled template: "
"ZeroDivisionError: division by zero"
),
},
),
],
)
async def test_test_condition_config_error(
hass: HomeAssistant,
websocket_client: MockHAClientWebSocket,
caplog: pytest.LogCaptureFixture,
condition: dict,
expected_error: dict,
) -> None:
"""Test condition config errors are reported to the client without logging."""
caplog.set_level(logging.ERROR)
await websocket_client.send_json_auto_id(
{"type": "test_condition", "condition": condition}
)
msg = await websocket_client.receive_json()
assert msg["type"] == const.TYPE_RESULT
assert not msg["success"]
assert msg["error"] == expected_error
# The expected error is not logged by the default websocket error handler
assert "Error handling message" not in caplog.text
async def test_test_condition_check_error_not_logged(
hass: HomeAssistant,
websocket_client: MockHAClientWebSocket,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test errors raised while checking the condition are not logged.
The condition is valid and instantiates fine, but checking it raises (here
the entity does not exist). The error is reported to the client without
being logged by the default websocket error handler.
"""
caplog.set_level(logging.ERROR)
await websocket_client.send_json_auto_id(
{
"type": "test_condition",
"condition": {
"condition": "state",
"entity_id": "hello.world",
"state": "paulus",
},
}
)
msg = await websocket_client.receive_json()
assert msg["type"] == const.TYPE_RESULT
assert not msg["success"]
assert msg["error"] == {
"code": "home_assistant_error",
"message": "In 'state':\n In 'state' condition: unknown entity hello.world",
}
assert "Error handling message" not in caplog.text
async def test_subscribe_condition(
hass: HomeAssistant,
websocket_client: MockHAClientWebSocket,
-254
View File
@@ -1,254 +0,0 @@
"""Tests for the domain constant checker."""
import astroid
from pylint.testutils import UnittestLinter
from pylint.utils.ast_walker import ASTWalker
from pylint_home_assistant.checkers.domain_constant import DomainConstantChecker
import pytest
from . import assert_no_messages
@pytest.fixture(name="domain_constant_checker")
def domain_constant_checker_fixture(
linter: UnittestLinter,
) -> DomainConstantChecker:
"""Fixture to provide a domain constant checker."""
return DomainConstantChecker(linter)
@pytest.mark.parametrize(
"code",
[
pytest.param(
"""
async_setup_component(hass, DOMAIN, {})
""",
id="name_domain_constant",
),
pytest.param(
"""
async_setup_component(hass, domain, {})
""",
id="name_domain_variable",
),
pytest.param(
"""
async_setup_component(hass, SENSOR_DOMAIN, {})
""",
id="name_prefixed_domain_constant",
),
pytest.param(
"""
async_setup_component(hass, sensor_domain, {})
""",
id="name_prefixed_domain_variable",
),
pytest.param(
"""
async_setup_component(hass, sensor.DOMAIN, {})
""",
id="attribute_domain_constant",
),
pytest.param(
"""
async_setup_component(hass, config_entry.domain, {})
""",
id="attribute_domain",
),
pytest.param(
"""
async_setup_component(hass, sensor.SENSOR_DOMAIN, {})
""",
id="attribute_prefixed_domain_constant",
),
pytest.param(
"""
async_setup_component(hass, "sensor", {})
""",
id="string_literal",
),
pytest.param(
"""
async_setup_component(hass, config["domain"], {})
""",
id="subscript_positional",
),
pytest.param(
"""
MockConfigEntry(domain=entries["domain"])
""",
id="subscript_kwarg",
),
pytest.param(
"""
async_mock_service(hass, DOMAIN, "service")
""",
id="async_mock_service",
),
pytest.param(
"""
MockConfigEntry(domain=DOMAIN)
""",
id="mock_config_entry_kwarg",
),
pytest.param(
"""
MockConfigEntry("other")
""",
id="mock_config_entry_positional_not_checked",
),
pytest.param(
"""
hass.services.async_call(DOMAIN, "service")
""",
id="services_async_call",
),
pytest.param(
"""
hass.services.call(DOMAIN, "service")
""",
id="services_call",
),
pytest.param(
"""
hass.config_entries.flow.async_init(DOMAIN)
""",
id="flow_async_init_positional",
),
pytest.param(
"""
hass.config_entries.flow.async_init(handler=DOMAIN)
""",
id="flow_async_init_kwarg",
),
pytest.param(
"""
some_other_function(hass, "other", {})
""",
id="unrelated_function",
),
pytest.param(
"""
hass.services.unrelated("other")
""",
id="unrelated_method",
),
],
)
def test_no_warning(
linter: UnittestLinter,
domain_constant_checker: DomainConstantChecker,
code: str,
) -> None:
"""Test cases that should not trigger a warning."""
root_node = astroid.parse(code, "tests.components.test_integration.test_init")
walker = ASTWalker(linter)
walker.add_checker(domain_constant_checker)
with assert_no_messages(linter):
walker.walk(root_node)
@pytest.mark.parametrize(
("code", "args"),
[
pytest.param(
"""
async_setup_component(hass, OTHER, {})
""",
("OTHER", "async_setup_component"),
id="name_not_domain",
),
pytest.param(
"""
async_setup_component(hass, sensor.OTHER, {})
""",
("sensor.OTHER", "async_setup_component"),
id="attribute_not_domain",
),
pytest.param(
"""
async_setup_component(hass, 5, {})
""",
("5", "async_setup_component"),
id="non_string_constant",
),
pytest.param(
"""
async_mock_service(hass, OTHER, "service")
""",
("OTHER", "async_mock_service"),
id="async_mock_service",
),
pytest.param(
"""
MockConfigEntry(domain=OTHER)
""",
("OTHER", "MockConfigEntry"),
id="mock_config_entry_kwarg",
),
pytest.param(
"""
hass.services.async_call(OTHER, "service")
""",
("OTHER", "hass.services.async_call"),
id="services_async_call",
),
pytest.param(
"""
hass.services.call(OTHER, "service")
""",
("OTHER", "hass.services.call"),
id="services_call",
),
pytest.param(
"""
hass.config_entries.flow.async_init(OTHER)
""",
("OTHER", "hass.config_entries.flow.async_init"),
id="flow_async_init_positional",
),
pytest.param(
"""
hass.config_entries.flow.async_init(handler=OTHER)
""",
("OTHER", "hass.config_entries.flow.async_init"),
id="flow_async_init_kwarg",
),
],
)
def test_domain_argument_flagged(
linter: UnittestLinter,
domain_constant_checker: DomainConstantChecker,
code: str,
args: tuple[str, str],
) -> None:
"""Test that non-domain arguments are flagged."""
root_node = astroid.parse(code, "tests.components.test_integration.test_init")
walker = ASTWalker(linter)
walker.add_checker(domain_constant_checker)
walker.walk(root_node)
messages = linter.release_messages()
assert len(messages) == 1
assert messages[0].msg_id == "home-assistant-domain-argument"
assert messages[0].args == args
def test_not_test_module_ignored(
linter: UnittestLinter,
domain_constant_checker: DomainConstantChecker,
) -> None:
"""Test that modules outside tests are ignored."""
root_node = astroid.parse(
"""
async_setup_component(hass, OTHER, {})
""",
"homeassistant.components.test_integration",
)
walker = ASTWalker(linter)
walker.add_checker(domain_constant_checker)
with assert_no_messages(linter):
walker.walk(root_node)