Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions homeassistant/components/bsblan/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@
ATTR_SATURDAY_SLOTS = "saturday_slots"
ATTR_SUNDAY_SLOTS = "sunday_slots"

# Service names
SERVICE_SET_HOT_WATER_SCHEDULE = "set_hot_water_schedule"
SERVICE_SYNC_TIME = "sync_time"


# Schema for a single time slot
_SLOT_SCHEMA = vol.Schema(
Expand Down Expand Up @@ -260,14 +256,14 @@ def async_setup_services(hass: HomeAssistant) -> None:
"""Register the BSB-LAN services."""
hass.services.async_register(
DOMAIN,
SERVICE_SET_HOT_WATER_SCHEDULE,
"set_hot_water_schedule",
set_hot_water_schedule,
schema=SERVICE_SET_HOT_WATER_SCHEDULE_SCHEMA,
)

hass.services.async_register(
DOMAIN,
SERVICE_SYNC_TIME,
"sync_time",
async_sync_time,
schema=SYNC_TIME_SCHEMA,
)
4 changes: 4 additions & 0 deletions homeassistant/components/frontend/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ async def async_user_store(hass: HomeAssistant, user_id: str) -> UserStore:
except BaseException as ex:
del stores[user_id]
future.set_exception(ex)
# Ensure the future is marked as retrieved
# since if there is no concurrent call it
# will otherwise never be retrieved.
future.exception()
raise
future.set_result(store)

Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/portainer/diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
from typing import Any

from homeassistant.components.diagnostics import async_redact_data
from homeassistant.const import CONF_API_TOKEN
from homeassistant.const import CONF_API_TOKEN, CONF_URL
from homeassistant.core import HomeAssistant

from . import PortainerConfigEntry
from .coordinator import PortainerCoordinator

TO_REDACT = [CONF_API_TOKEN]
TO_REDACT = [CONF_API_TOKEN, CONF_URL]


def _serialize_coordinator(coordinator: PortainerCoordinator) -> dict[str, Any]:
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/switchbot/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,5 @@
"iot_class": "local_push",
"loggers": ["switchbot"],
"quality_scale": "gold",
"requirements": ["PySwitchbot==1.0.0"]
"requirements": ["PySwitchbot==1.1.0"]
}
43 changes: 0 additions & 43 deletions homeassistant/components/vacuum/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,12 +236,6 @@ def add_to_platform_start(
if self.__vacuum_legacy_battery_icon:
self._report_deprecated_battery_properties("battery_icon")

@callback
def async_write_ha_state(self) -> None:
"""Write the state to the state machine."""
super().async_write_ha_state()
self._async_check_segments_issues()

@callback
def async_registry_entry_updated(self) -> None:
"""Run when the entity registry entry has been updated."""
Expand Down Expand Up @@ -514,43 +508,6 @@ def _async_check_segments_issues(self) -> None:
return

options: Mapping[str, Any] = self.registry_entry.options.get(DOMAIN, {})
should_have_not_configured_issue = (
VacuumEntityFeature.CLEAN_AREA in self.supported_features
and options.get("area_mapping") is None
)

if (
should_have_not_configured_issue
and not self._segments_not_configured_issue_created
):
issue_id = (
f"{ISSUE_SEGMENTS_MAPPING_NOT_CONFIGURED}_{self.registry_entry.id}"
)
ir.async_create_issue(
self.hass,
DOMAIN,
issue_id,
data={
"entry_id": self.registry_entry.id,
"entity_id": self.entity_id,
},
is_fixable=False,
severity=ir.IssueSeverity.WARNING,
translation_key=ISSUE_SEGMENTS_MAPPING_NOT_CONFIGURED,
translation_placeholders={
"entity_id": self.entity_id,
},
)
self._segments_not_configured_issue_created = True
elif (
not should_have_not_configured_issue
and self._segments_not_configured_issue_created
):
issue_id = (
f"{ISSUE_SEGMENTS_MAPPING_NOT_CONFIGURED}_{self.registry_entry.id}"
)
ir.async_delete_issue(self.hass, DOMAIN, issue_id)
self._segments_not_configured_issue_created = False

if self._segments_changed_last_seen is not None and (
VacuumEntityFeature.CLEAN_AREA not in self.supported_features
Expand Down
4 changes: 0 additions & 4 deletions homeassistant/components/vacuum/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,6 @@
"segments_changed": {
"description": "",
"title": "Vacuum segments have changed for {entity_id}"
},
"segments_mapping_not_configured": {
"description": "",
"title": "Vacuum segment mapping not configured for {entity_id}"
}
},
"selector": {
Expand Down
5 changes: 4 additions & 1 deletion homeassistant/util/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,11 @@ def write_utf8_file_atomic(
Using this function frequently will significantly
negatively impact performance.
"""
encoding = "utf-8" if "b" not in mode else None
try:
with AtomicWriter(filename, mode=mode, overwrite=True).open() as fdesc:
with AtomicWriter( # type: ignore[call-arg] # atomicwrites-stubs is outdated, encoding is a valid kwarg
filename, mode=mode, overwrite=True, encoding=encoding
).open() as fdesc:
if not private:
os.fchmod(fdesc.fileno(), 0o644)
fdesc.write(utf8_data)
Expand Down
2 changes: 1 addition & 1 deletion requirements_all.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion requirements_test_all.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 14 additions & 31 deletions tests/components/bsblan/test_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@
import voluptuous as vol

from homeassistant.components.bsblan.const import DOMAIN
from homeassistant.components.bsblan.services import (
SERVICE_SET_HOT_WATER_SCHEDULE,
async_setup_services,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import device_registry as dr
Expand Down Expand Up @@ -134,7 +130,7 @@ async def test_set_hot_water_schedule(

await hass.services.async_call(
DOMAIN,
SERVICE_SET_HOT_WATER_SCHEDULE,
"set_hot_water_schedule",
service_call_data,
blocking=True,
)
Expand Down Expand Up @@ -163,7 +159,7 @@ async def test_invalid_device_id(
with pytest.raises(ServiceValidationError) as exc_info:
await hass.services.async_call(
DOMAIN,
SERVICE_SET_HOT_WATER_SCHEDULE,
"set_hot_water_schedule",
{
"device_id": "invalid_device_id",
"monday_slots": [
Expand All @@ -176,11 +172,12 @@ async def test_invalid_device_id(
assert exc_info.value.translation_key == "invalid_device_id"


@pytest.mark.usefixtures("setup_integration")
@pytest.mark.parametrize(
("service_name", "service_data"),
[
(
SERVICE_SET_HOT_WATER_SCHEDULE,
"set_hot_water_schedule",
{"monday_slots": [{"start_time": time(6, 0), "end_time": time(8, 0)}]},
),
("sync_time", {}),
Expand All @@ -205,9 +202,6 @@ async def test_no_config_entry_for_device(
name="Other Device",
)

# Register the bsblan service without setting up any bsblan config entry
async_setup_services(hass)

with pytest.raises(ServiceValidationError) as exc_info:
await hass.services.async_call(
DOMAIN,
Expand All @@ -222,26 +216,15 @@ async def test_no_config_entry_for_device(
async def test_config_entry_not_loaded(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
device_registry: dr.DeviceRegistry,
device_entry: dr.DeviceEntry,
) -> None:
"""Test error when config entry is not loaded."""
# Add the config entry but don't set it up (so it stays in NOT_LOADED state)
mock_config_entry.add_to_hass(hass)

# Create the device manually since setup won't run
device_entry = device_registry.async_get_or_create(
config_entry_id=mock_config_entry.entry_id,
identifiers={(DOMAIN, TEST_DEVICE_MAC)},
name="BSB-LAN Device",
)

# Register the service
async_setup_services(hass)
await hass.config_entries.async_unload(mock_config_entry.entry_id)

with pytest.raises(ServiceValidationError) as exc_info:
await hass.services.async_call(
DOMAIN,
SERVICE_SET_HOT_WATER_SCHEDULE,
"set_hot_water_schedule",
{
"device_id": device_entry.id,
"monday_slots": [
Expand All @@ -266,7 +249,7 @@ async def test_api_error(
with pytest.raises(HomeAssistantError) as exc_info:
await hass.services.async_call(
DOMAIN,
SERVICE_SET_HOT_WATER_SCHEDULE,
"set_hot_water_schedule",
{
"device_id": device_entry.id,
"monday_slots": [
Expand Down Expand Up @@ -302,7 +285,7 @@ async def test_time_validation_errors(
with pytest.raises(ServiceValidationError) as exc_info:
await hass.services.async_call(
DOMAIN,
SERVICE_SET_HOT_WATER_SCHEDULE,
"set_hot_water_schedule",
{
"device_id": device_entry.id,
"monday_slots": [
Expand All @@ -325,7 +308,7 @@ async def test_unprovided_days_are_none(
# Only provide Monday and Tuesday, leave other days unprovided
await hass.services.async_call(
DOMAIN,
SERVICE_SET_HOT_WATER_SCHEDULE,
"set_hot_water_schedule",
{
"device_id": device_entry.id,
"monday_slots": [
Expand Down Expand Up @@ -369,7 +352,7 @@ async def test_string_time_formats(
# Test with string time formats
await hass.services.async_call(
DOMAIN,
SERVICE_SET_HOT_WATER_SCHEDULE,
"set_hot_water_schedule",
{
"device_id": device_entry.id,
"monday_slots": [
Expand Down Expand Up @@ -406,7 +389,7 @@ async def test_non_standard_time_types(
with pytest.raises(vol.MultipleInvalid):
await hass.services.async_call(
DOMAIN,
SERVICE_SET_HOT_WATER_SCHEDULE,
"set_hot_water_schedule",
{
"device_id": device_entry.id,
"monday_slots": [
Expand All @@ -424,15 +407,15 @@ async def test_async_setup_services(
) -> None:
"""Test service registration."""
# Verify service doesn't exist initially
assert not hass.services.has_service(DOMAIN, SERVICE_SET_HOT_WATER_SCHEDULE)
assert not hass.services.has_service(DOMAIN, "set_hot_water_schedule")

# Set up the integration
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()

# Verify service is now registered
assert hass.services.has_service(DOMAIN, SERVICE_SET_HOT_WATER_SCHEDULE)
assert hass.services.has_service(DOMAIN, "set_hot_water_schedule")


async def test_sync_time_service(
Expand Down
2 changes: 1 addition & 1 deletion tests/components/portainer/snapshots/test_diagnostics.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
'config_entry': dict({
'data': dict({
'api_token': '**REDACTED**',
'url': 'https://127.0.0.1:9000/',
'url': '**REDACTED**',
'verify_ssl': True,
}),
'disabled_by': None,
Expand Down
74 changes: 0 additions & 74 deletions tests/components/vacuum/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,80 +487,6 @@ async def test_segments_changed_issue(
assert ir.async_get(hass).async_get_issue(DOMAIN, issue_id) is None


@pytest.mark.usefixtures("config_flow_fixture")
@pytest.mark.parametrize("area_mapping", [{"area_1": ["seg_1"]}, {}])
async def test_segments_mapping_not_configured_issue(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
area_mapping: dict[str, list[str]],
) -> None:
"""Test segments_mapping_not_configured issue."""
mock_vacuum = MockVacuumWithCleanArea(name="Testing", entity_id="vacuum.testing")

config_entry = MockConfigEntry(domain="test")
config_entry.add_to_hass(hass)

mock_integration(
hass,
MockModule(
"test",
async_setup_entry=help_async_setup_entry_init,
async_unload_entry=help_async_unload_entry,
),
)
setup_test_component_platform(hass, DOMAIN, [mock_vacuum], from_config_entry=True)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()

entity_entry = entity_registry.async_get(mock_vacuum.entity_id)

issue_id = f"segments_mapping_not_configured_{entity_entry.id}"
issue = ir.async_get(hass).async_get_issue(DOMAIN, issue_id)
assert issue is not None
assert issue.severity == ir.IssueSeverity.WARNING
assert issue.translation_key == "segments_mapping_not_configured"

entity_registry.async_update_entity_options(
mock_vacuum.entity_id,
DOMAIN,
{
"area_mapping": area_mapping,
"last_seen_segments": [asdict(segment) for segment in mock_vacuum.segments],
},
)
await hass.async_block_till_done()

assert ir.async_get(hass).async_get_issue(DOMAIN, issue_id) is None


@pytest.mark.usefixtures("config_flow_fixture")
async def test_no_segments_mapping_issue_without_clean_area(
hass: HomeAssistant,
) -> None:
"""Test no repair issue is created when CLEAN_AREA is not supported."""
mock_vacuum = MockVacuum(name="Testing", entity_id="vacuum.testing")

config_entry = MockConfigEntry(domain="test")
config_entry.add_to_hass(hass)

mock_integration(
hass,
MockModule(
"test",
async_setup_entry=help_async_setup_entry_init,
async_unload_entry=help_async_unload_entry,
),
)
setup_test_component_platform(hass, DOMAIN, [mock_vacuum], from_config_entry=True)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()

issues = ir.async_get(hass).issues
assert not any(
issue_id[1].startswith("segments_mapping_not_configured") for issue_id in issues
)


@pytest.mark.parametrize(("is_built_in", "log_warnings"), [(True, 0), (False, 3)])
async def test_vacuum_log_deprecated_battery_using_properties(
hass: HomeAssistant,
Expand Down
Loading
Loading