Compare commits

..

6 commits

Author SHA1 Message Date
epenet
f4d1eafb20 Ignore repairs 2024-11-14 15:30:21 +00:00
epenet
b79fd62a14 Ignore test translations 2024-11-14 15:30:21 +00:00
epenet
26685cd75c Adjust comment 2024-11-14 15:30:21 +00:00
epenet
1539f19a10 Fix reference to task 2024-11-14 15:30:21 +00:00
epenet
cecf5d02ae description is only required on non-fixable flows 2024-11-14 15:30:21 +00:00
epenet
4ca3d7a1f8 Add translation checks for issue registry 2024-11-14 15:30:21 +00:00
9 changed files with 208 additions and 59 deletions

View file

@ -515,7 +515,7 @@ async def async_from_config_dict(
issue_registry.async_create_issue( issue_registry.async_create_issue(
hass, hass,
core.DOMAIN, core.DOMAIN,
f"python_version_{required_python_version}", "python_version",
is_fixable=False, is_fixable=False,
severity=issue_registry.IssueSeverity.WARNING, severity=issue_registry.IssueSeverity.WARNING,
breaks_in_ha_version=REQUIRED_NEXT_PYTHON_HA_RELEASE, breaks_in_ha_version=REQUIRED_NEXT_PYTHON_HA_RELEASE,

View file

@ -1,5 +1,5 @@
# serializer version: 1 # serializer version: 1
# name: test_buttons[button.lunar_ddeeff_reset_timer-entry] # name: test_buttons[entry_button_reset_timer]
EntityRegistryEntrySnapshot({ EntityRegistryEntrySnapshot({
'aliases': set({ 'aliases': set({
}), }),
@ -32,20 +32,7 @@
'unit_of_measurement': None, 'unit_of_measurement': None,
}) })
# --- # ---
# name: test_buttons[button.lunar_ddeeff_reset_timer-state] # name: test_buttons[entry_button_start_stop_timer]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'LUNAR-DDEEFF Reset timer',
}),
'context': <ANY>,
'entity_id': 'button.lunar_ddeeff_reset_timer',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_buttons[button.lunar_ddeeff_start_stop_timer-entry]
EntityRegistryEntrySnapshot({ EntityRegistryEntrySnapshot({
'aliases': set({ 'aliases': set({
}), }),
@ -78,20 +65,7 @@
'unit_of_measurement': None, 'unit_of_measurement': None,
}) })
# --- # ---
# name: test_buttons[button.lunar_ddeeff_start_stop_timer-state] # name: test_buttons[entry_button_tare]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'LUNAR-DDEEFF Start/stop timer',
}),
'context': <ANY>,
'entity_id': 'button.lunar_ddeeff_start_stop_timer',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_buttons[button.lunar_ddeeff_tare-entry]
EntityRegistryEntrySnapshot({ EntityRegistryEntrySnapshot({
'aliases': set({ 'aliases': set({
}), }),
@ -124,7 +98,33 @@
'unit_of_measurement': None, 'unit_of_measurement': None,
}) })
# --- # ---
# name: test_buttons[button.lunar_ddeeff_tare-state] # name: test_buttons[state_button_reset_timer]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'LUNAR-DDEEFF Reset timer',
}),
'context': <ANY>,
'entity_id': 'button.lunar_ddeeff_reset_timer',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_buttons[state_button_start_stop_timer]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'LUNAR-DDEEFF Start/stop timer',
}),
'context': <ANY>,
'entity_id': 'button.lunar_ddeeff_start_stop_timer',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_buttons[state_button_tare]
StateSnapshot({ StateSnapshot({
'attributes': ReadOnlyDict({ 'attributes': ReadOnlyDict({
'friendly_name': 'LUNAR-DDEEFF Tare', 'friendly_name': 'LUNAR-DDEEFF Tare',

View file

@ -1,24 +1,21 @@
"""Tests for the acaia buttons.""" """Tests for the acaia buttons."""
from datetime import timedelta from datetime import timedelta
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock
from freezegun.api import FrozenDateTimeFactory from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy import SnapshotAssertion from syrupy import SnapshotAssertion
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS
from homeassistant.const import ( from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE, STATE_UNKNOWN
ATTR_ENTITY_ID,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
Platform,
)
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
from . import setup_integration from tests.common import async_fire_time_changed
pytestmark = pytest.mark.usefixtures("init_integration")
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
BUTTONS = ( BUTTONS = (
"tare", "tare",
@ -31,25 +28,24 @@ async def test_buttons(
hass: HomeAssistant, hass: HomeAssistant,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
mock_scale: MagicMock,
mock_config_entry: MockConfigEntry,
) -> None: ) -> None:
"""Test the acaia buttons.""" """Test the acaia buttons."""
for button in BUTTONS:
state = hass.states.get(f"button.lunar_ddeeff_{button}")
assert state
assert state == snapshot(name=f"state_button_{button}")
with patch("homeassistant.components.acaia.PLATFORMS", [Platform.BUTTON]): entry = entity_registry.async_get(state.entity_id)
await setup_integration(hass, mock_config_entry) assert entry
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) assert entry == snapshot(name=f"entry_button_{button}")
async def test_button_presses( async def test_button_presses(
hass: HomeAssistant, hass: HomeAssistant,
mock_scale: MagicMock, mock_scale: MagicMock,
mock_config_entry: MockConfigEntry,
) -> None: ) -> None:
"""Test the acaia button presses.""" """Test the acaia button presses."""
await setup_integration(hass, mock_config_entry)
for button in BUTTONS: for button in BUTTONS:
await hass.services.async_call( await hass.services.async_call(
BUTTON_DOMAIN, BUTTON_DOMAIN,
@ -67,13 +63,10 @@ async def test_button_presses(
async def test_buttons_unavailable_on_disconnected_scale( async def test_buttons_unavailable_on_disconnected_scale(
hass: HomeAssistant, hass: HomeAssistant,
mock_scale: MagicMock, mock_scale: MagicMock,
mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
) -> None: ) -> None:
"""Test the acaia buttons are unavailable when the scale is disconnected.""" """Test the acaia buttons are unavailable when the scale is disconnected."""
await setup_integration(hass, mock_config_entry)
for button in BUTTONS: for button in BUTTONS:
state = hass.states.get(f"button.lunar_ddeeff_{button}") state = hass.states.get(f"button.lunar_ddeeff_{button}")
assert state assert state

View file

@ -2,7 +2,8 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable, Generator import asyncio
from collections.abc import AsyncGenerator, Callable, Generator
from importlib.util import find_spec from importlib.util import find_spec
from pathlib import Path from pathlib import Path
import string import string
@ -32,6 +33,7 @@ from homeassistant.data_entry_flow import (
FlowManager, FlowManager,
FlowResultType, FlowResultType,
) )
from homeassistant.helpers import issue_registry as ir
from homeassistant.helpers.translation import async_get_translations from homeassistant.helpers.translation import async_get_translations
if TYPE_CHECKING: if TYPE_CHECKING:
@ -670,20 +672,53 @@ async def _check_config_flow_result_translations(
) )
async def _check_create_issue_translations(
issue_registry: ir.IssueRegistry,
result: ir.IssueEntry,
ignore_translations: dict[str, str],
) -> None:
if result.translation_key is None:
return
await _validate_translation(
issue_registry.hass,
ignore_translations,
"issues",
result.domain,
f"{result.translation_key}.title",
result.translation_placeholders,
)
if result.is_fixable:
return
await _validate_translation(
issue_registry.hass,
ignore_translations,
"issues",
result.domain,
f"{result.translation_key}.description",
result.translation_placeholders,
)
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def check_translations(ignore_translations: str | list[str]) -> Generator[None]: async def check_translations(
ignore_translations: str | list[str],
) -> AsyncGenerator[None]:
"""Check that translation requirements are met. """Check that translation requirements are met.
Current checks: Current checks:
- data entry flow results (ConfigFlow/OptionsFlow) - data entry flow results (ConfigFlow/OptionsFlow)
- issue registry entries
""" """
if not isinstance(ignore_translations, list): if not isinstance(ignore_translations, list):
ignore_translations = [ignore_translations] ignore_translations = [ignore_translations]
_ignore_translations = {k: "unused" for k in ignore_translations} _ignore_translations = {k: "unused" for k in ignore_translations}
translation_tasks = set[asyncio.Task[None]]()
# Keep reference to original functions # Keep reference to original functions
_original_flow_manager_async_handle_step = FlowManager._async_handle_step _original_flow_manager_async_handle_step = FlowManager._async_handle_step
_original_issue_registry_async_create_issue = ir.IssueRegistry.async_get_or_create
# Prepare override functions # Prepare override functions
async def _flow_manager_async_handle_step( async def _flow_manager_async_handle_step(
@ -695,13 +730,36 @@ def check_translations(ignore_translations: str | list[str]) -> Generator[None]:
) )
return result return result
def _issue_registry_async_create_issue(
self: ir.IssueRegistry, domain: str, issue_id: str, *args, **kwargs
) -> None:
result = _original_issue_registry_async_create_issue(
self, domain, issue_id, *args, **kwargs
)
translation_tasks.add(
self.hass.async_create_task_internal(
_check_create_issue_translations(self, result, _ignore_translations),
"Check create_issue translations",
eager_start=True,
)
)
return result
# Use override functions # Use override functions
with patch( with (
"homeassistant.data_entry_flow.FlowManager._async_handle_step", patch(
_flow_manager_async_handle_step, "homeassistant.data_entry_flow.FlowManager._async_handle_step",
_flow_manager_async_handle_step,
),
patch(
"homeassistant.helpers.issue_registry.IssueRegistry.async_get_or_create",
_issue_registry_async_create_issue,
),
): ):
yield yield
await asyncio.gather(*translation_tasks)
# Run final checks # Run final checks
unused_ignore = [k for k, v in _ignore_translations.items() if v == "unused"] unused_ignore = [k for k, v in _ignore_translations.items() if v == "unused"]
if unused_ignore: if unused_ignore:

View file

@ -21,6 +21,16 @@ from tests.common import mock_platform
from tests.typing import WebSocketGenerator from tests.typing import WebSocketGenerator
@pytest.mark.parametrize(
"ignore_translations",
[
[
"component.test.issues.even_worse.title",
"component.test.issues.even_worse.description",
"component.test.issues.abc_123.title",
]
],
)
@pytest.mark.freeze_time("2022-07-19 07:53:05") @pytest.mark.freeze_time("2022-07-19 07:53:05")
async def test_create_update_issue( async def test_create_update_issue(
hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, hass_ws_client: WebSocketGenerator
@ -160,6 +170,14 @@ async def test_create_issue_invalid_version(
assert msg["result"] == {"issues": []} assert msg["result"] == {"issues": []}
@pytest.mark.parametrize(
"ignore_translations",
[
[
"component.test.issues.abc_123.title",
]
],
)
@pytest.mark.freeze_time("2022-07-19 07:53:05") @pytest.mark.freeze_time("2022-07-19 07:53:05")
async def test_ignore_issue( async def test_ignore_issue(
hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, hass_ws_client: WebSocketGenerator
@ -329,6 +347,10 @@ async def test_ignore_issue(
} }
@pytest.mark.parametrize(
"ignore_translations",
["component.fake_integration.issues.abc_123.title"],
)
@pytest.mark.freeze_time("2022-07-19 07:53:05") @pytest.mark.freeze_time("2022-07-19 07:53:05")
async def test_delete_issue( async def test_delete_issue(
hass: HomeAssistant, hass: HomeAssistant,
@ -483,6 +505,10 @@ async def test_non_compliant_platform(
assert list(hass.data[DOMAIN]["platforms"].keys()) == ["fake_integration"] assert list(hass.data[DOMAIN]["platforms"].keys()) == ["fake_integration"]
@pytest.mark.parametrize(
"ignore_translations",
["component.fake_integration.issues.abc_123.title"],
)
@pytest.mark.freeze_time("2022-07-21 08:22:00") @pytest.mark.freeze_time("2022-07-21 08:22:00")
async def test_sync_methods( async def test_sync_methods(
hass: HomeAssistant, hass: HomeAssistant,

View file

@ -151,6 +151,10 @@ async def mock_repairs_integration(hass: HomeAssistant) -> None:
) )
@pytest.mark.parametrize(
"ignore_translations",
["component.fake_integration.issues.abc_123.title"],
)
async def test_dismiss_issue( async def test_dismiss_issue(
hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, hass_ws_client: WebSocketGenerator
) -> None: ) -> None:
@ -234,6 +238,10 @@ async def test_dismiss_issue(
} }
@pytest.mark.parametrize(
"ignore_translations",
["component.fake_integration.issues.abc_123.title"],
)
async def test_fix_non_existing_issue( async def test_fix_non_existing_issue(
hass: HomeAssistant, hass: HomeAssistant,
hass_client: ClientSessionGenerator, hass_client: ClientSessionGenerator,
@ -281,10 +289,20 @@ async def test_fix_non_existing_issue(
@pytest.mark.parametrize( @pytest.mark.parametrize(
("domain", "step", "description_placeholders"), ("domain", "step", "description_placeholders", "ignore_translations"),
[ [
("fake_integration", "custom_step", None), (
("fake_integration_default_handler", "confirm", {"abc": "123"}), "fake_integration",
"custom_step",
None,
["component.fake_integration.issues.abc_123.title"],
),
(
"fake_integration_default_handler",
"confirm",
{"abc": "123"},
["component.fake_integration_default_handler.issues.abc_123.title"],
),
], ],
) )
async def test_fix_issue( async def test_fix_issue(
@ -380,6 +398,10 @@ async def test_fix_issue_unauth(
assert resp.status == HTTPStatus.UNAUTHORIZED assert resp.status == HTTPStatus.UNAUTHORIZED
@pytest.mark.parametrize(
"ignore_translations",
["component.fake_integration.issues.abc_123.title"],
)
async def test_get_progress_unauth( async def test_get_progress_unauth(
hass: HomeAssistant, hass: HomeAssistant,
hass_client: ClientSessionGenerator, hass_client: ClientSessionGenerator,
@ -411,6 +433,10 @@ async def test_get_progress_unauth(
assert resp.status == HTTPStatus.UNAUTHORIZED assert resp.status == HTTPStatus.UNAUTHORIZED
@pytest.mark.parametrize(
"ignore_translations",
["component.fake_integration.issues.abc_123.title"],
)
async def test_step_unauth( async def test_step_unauth(
hass: HomeAssistant, hass: HomeAssistant,
hass_client: ClientSessionGenerator, hass_client: ClientSessionGenerator,
@ -442,6 +468,16 @@ async def test_step_unauth(
assert resp.status == HTTPStatus.UNAUTHORIZED assert resp.status == HTTPStatus.UNAUTHORIZED
@pytest.mark.parametrize(
"ignore_translations",
[
[
"component.test.issues.even_worse.title",
"component.test.issues.even_worse.description",
"component.test.issues.abc_123.title",
]
],
)
@pytest.mark.freeze_time("2022-07-19 07:53:05") @pytest.mark.freeze_time("2022-07-19 07:53:05")
async def test_list_issues( async def test_list_issues(
hass: HomeAssistant, hass: HomeAssistant,
@ -533,6 +569,10 @@ async def test_list_issues(
} }
@pytest.mark.parametrize(
"ignore_translations",
["component.fake_integration.issues.abc_123.title"],
)
async def test_fix_issue_aborted( async def test_fix_issue_aborted(
hass: HomeAssistant, hass: HomeAssistant,
hass_client: ClientSessionGenerator, hass_client: ClientSessionGenerator,
@ -594,6 +634,16 @@ async def test_fix_issue_aborted(
assert msg["result"]["issues"][0] == first_issue assert msg["result"]["issues"][0] == first_issue
@pytest.mark.parametrize(
"ignore_translations",
[
[
"component.test.issues.abc_123.title",
"component.test.issues.even_worse.title",
"component.test.issues.even_worse.description",
]
],
)
@pytest.mark.freeze_time("2022-07-19 07:53:05") @pytest.mark.freeze_time("2022-07-19 07:53:05")
async def test_get_issue_data( async def test_get_issue_data(
hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, hass_ws_client: WebSocketGenerator

View file

@ -5432,6 +5432,17 @@ async def test_exclude_attributes(hass: HomeAssistant) -> None:
assert ATTR_FRIENDLY_NAME in states[0].attributes assert ATTR_FRIENDLY_NAME in states[0].attributes
@pytest.mark.parametrize(
"ignore_translations",
[
[
"component.test.issues..title",
"component.test.issues..description",
"component.sensor.issues..title",
"component.sensor.issues..description",
]
],
)
async def test_clean_up_repairs( async def test_clean_up_repairs(
hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, hass_ws_client: WebSocketGenerator
) -> None: ) -> None:

View file

@ -2,6 +2,8 @@
from __future__ import annotations from __future__ import annotations
import pytest
from homeassistant.components.workday.const import CONF_REMOVE_HOLIDAYS, DOMAIN from homeassistant.components.workday.const import CONF_REMOVE_HOLIDAYS, DOMAIN
from homeassistant.const import CONF_COUNTRY from homeassistant.const import CONF_COUNTRY
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -427,6 +429,10 @@ async def test_bad_date_holiday(
assert issue assert issue
@pytest.mark.parametrize(
"ignore_translations",
["component.workday.issues.issue_1.title"],
)
async def test_other_fixable_issues( async def test_other_fixable_issues(
hass: HomeAssistant, hass: HomeAssistant,
hass_client: ClientSessionGenerator, hass_client: ClientSessionGenerator,

View file

@ -3,6 +3,7 @@
from copy import deepcopy from copy import deepcopy
from unittest.mock import patch from unittest.mock import patch
import pytest
from zwave_js_server.event import Event from zwave_js_server.event import Event
from zwave_js_server.model.node import Node from zwave_js_server.model.node import Node
@ -179,6 +180,10 @@ async def test_device_config_file_changed_ignore_step(
assert msg["result"]["issues"][0].get("dismissed_version") is not None assert msg["result"]["issues"][0].get("dismissed_version") is not None
@pytest.mark.parametrize(
"ignore_translations",
["component.zwave_js.issues.invalid_issue.title"],
)
async def test_invalid_issue( async def test_invalid_issue(
hass: HomeAssistant, hass: HomeAssistant,
hass_client: ClientSessionGenerator, hass_client: ClientSessionGenerator,