mirror of
https://github.com/home-assistant/core.git
synced 2026-08-07 13:55:32 +01:00
587 lines
19 KiB
Python
587 lines
19 KiB
Python
"""Tests for iZone config entry setup and unload."""
|
|
|
|
from asyncio import CancelledError
|
|
from unittest.mock import AsyncMock, Mock, patch
|
|
|
|
from pizone import ControllerCommandError, ControllerEndpoint, UnpairedBridgeError
|
|
import pytest
|
|
|
|
from homeassistant.components.izone.const import DOMAIN
|
|
from homeassistant.config_entries import ConfigEntryState
|
|
from homeassistant.const import CONF_HOST
|
|
from homeassistant.core import HomeAssistant
|
|
|
|
from .conftest import async_load_yaml_exclude, create_mock_controller
|
|
|
|
from tests.common import MockConfigEntry
|
|
|
|
|
|
async def test_setup_and_unload(
|
|
hass: HomeAssistant,
|
|
mock_config_entry: MockConfigEntry,
|
|
mock_create_discovery: AsyncMock,
|
|
mock_discovery_service: Mock,
|
|
mock_controller: Mock,
|
|
) -> None:
|
|
"""Entry loads climate entities and unloads cleanly."""
|
|
mock_config_entry.add_to_hass(hass)
|
|
|
|
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
assert mock_config_entry.state is ConfigEntryState.LOADED
|
|
mock_discovery_service.create_controller.assert_awaited_once()
|
|
mock_controller.refresh_all.assert_awaited()
|
|
assert hass.states.get("climate.izone_controller_000000001") is not None
|
|
|
|
assert await hass.config_entries.async_unload(mock_config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
assert mock_config_entry.state is ConfigEntryState.NOT_LOADED
|
|
mock_controller.close.assert_awaited()
|
|
mock_discovery_service.close.assert_awaited()
|
|
|
|
|
|
async def test_setup_heals_legacy_domain_unique_id(
|
|
hass: HomeAssistant,
|
|
mock_create_discovery: AsyncMock,
|
|
mock_discovery_service: Mock,
|
|
mock_controller: Mock,
|
|
) -> None:
|
|
"""Legacy unique_id=DOMAIN binds the sole discovered endpoint and loads."""
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id=DOMAIN,
|
|
title="iZone Aircon",
|
|
data={},
|
|
version=2,
|
|
)
|
|
entry.add_to_hass(hass)
|
|
mock_discovery_service.discover_all = AsyncMock(
|
|
return_value=[ControllerEndpoint(uid="000000001", host="192.0.2.1")]
|
|
)
|
|
|
|
assert await hass.config_entries.async_setup(entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
assert entry.state is ConfigEntryState.LOADED
|
|
assert entry.unique_id == "000000001"
|
|
assert entry.data[CONF_HOST] == "192.0.2.1"
|
|
assert entry.title == "iZone 000000001"
|
|
mock_discovery_service.create_controller.assert_awaited_once()
|
|
|
|
|
|
async def test_setup_heals_legacy_domain_unique_id_keeps_custom_title(
|
|
hass: HomeAssistant,
|
|
mock_create_discovery: AsyncMock,
|
|
mock_discovery_service: Mock,
|
|
mock_controller: Mock,
|
|
) -> None:
|
|
"""Legacy rebind keeps a user-customised title instead of rewriting it."""
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id=DOMAIN,
|
|
title="Living Room AC",
|
|
data={},
|
|
version=2,
|
|
)
|
|
entry.add_to_hass(hass)
|
|
mock_discovery_service.discover_all = AsyncMock(
|
|
return_value=[ControllerEndpoint(uid="000000001", host="192.0.2.1")]
|
|
)
|
|
|
|
assert await hass.config_entries.async_setup(entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
assert entry.state is ConfigEntryState.LOADED
|
|
assert entry.unique_id == "000000001"
|
|
assert entry.title == "Living Room AC"
|
|
|
|
|
|
async def test_setup_legacy_domain_unique_id_filters_yaml_excluded(
|
|
hass: HomeAssistant,
|
|
mock_create_discovery: AsyncMock,
|
|
mock_discovery_service: Mock,
|
|
mock_controller: Mock,
|
|
) -> None:
|
|
"""YAML exclude narrows multiple discoveries to one eligible controller."""
|
|
await async_load_yaml_exclude(hass, "000000002")
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id=DOMAIN,
|
|
title="iZone Aircon",
|
|
data={},
|
|
version=2,
|
|
)
|
|
entry.add_to_hass(hass)
|
|
mock_discovery_service.discover_all = AsyncMock(
|
|
return_value=[
|
|
ControllerEndpoint(uid="000000001", host="192.0.2.1"),
|
|
ControllerEndpoint(uid="000000002", host="192.0.2.2"),
|
|
]
|
|
)
|
|
|
|
assert await hass.config_entries.async_setup(entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
assert entry.state is ConfigEntryState.LOADED
|
|
assert entry.unique_id == "000000001"
|
|
assert entry.data[CONF_HOST] == "192.0.2.1"
|
|
|
|
|
|
async def test_setup_legacy_domain_unique_id_filters_already_configured(
|
|
hass: HomeAssistant,
|
|
mock_create_discovery: AsyncMock,
|
|
mock_discovery_service: Mock,
|
|
mock_controller: Mock,
|
|
) -> None:
|
|
"""Already-configured UIDs are skipped when binding a legacy DOMAIN entry."""
|
|
configured = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id="000000002",
|
|
data={CONF_HOST: "192.0.2.2"},
|
|
version=2,
|
|
)
|
|
configured.add_to_hass(hass)
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id=DOMAIN,
|
|
title="iZone Aircon",
|
|
data={},
|
|
version=2,
|
|
)
|
|
entry.add_to_hass(hass)
|
|
mock_discovery_service.discover_all = AsyncMock(
|
|
return_value=[
|
|
ControllerEndpoint(uid="000000001", host="192.0.2.1"),
|
|
ControllerEndpoint(uid="000000002", host="192.0.2.2"),
|
|
]
|
|
)
|
|
|
|
assert await hass.config_entries.async_setup(entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
assert entry.state is ConfigEntryState.LOADED
|
|
assert entry.unique_id == "000000001"
|
|
assert entry.data[CONF_HOST] == "192.0.2.1"
|
|
|
|
|
|
async def test_migrate_then_heals_legacy_domain_unique_id(
|
|
hass: HomeAssistant,
|
|
mock_create_discovery: AsyncMock,
|
|
mock_discovery_service: Mock,
|
|
mock_controller: Mock,
|
|
) -> None:
|
|
"""v1 migrate clears data, then setup heal rebinds UID and CONF_HOST."""
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
version=1,
|
|
unique_id=DOMAIN,
|
|
title="iZone Aircon",
|
|
data={"host": "203.0.113.1"},
|
|
)
|
|
entry.add_to_hass(hass)
|
|
mock_discovery_service.discover_all = AsyncMock(
|
|
return_value=[ControllerEndpoint(uid="000000001", host="192.0.2.1")]
|
|
)
|
|
|
|
assert await hass.config_entries.async_setup(entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
assert entry.version == 2
|
|
assert entry.state is ConfigEntryState.LOADED
|
|
assert entry.unique_id == "000000001"
|
|
assert entry.data == {CONF_HOST: "192.0.2.1"}
|
|
assert entry.title == "iZone 000000001"
|
|
|
|
|
|
async def test_setup_legacy_domain_unique_id_no_eligible_retries(
|
|
hass: HomeAssistant,
|
|
mock_create_discovery: AsyncMock,
|
|
mock_discovery_service: Mock,
|
|
) -> None:
|
|
"""Legacy unique_id=DOMAIN with no eligible endpoint leaves SETUP_RETRY."""
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id=DOMAIN,
|
|
title="iZone Aircon",
|
|
data={},
|
|
version=2,
|
|
)
|
|
entry.add_to_hass(hass)
|
|
mock_discovery_service.discover_all = AsyncMock(return_value=[])
|
|
|
|
assert not await hass.config_entries.async_setup(entry.entry_id)
|
|
assert entry.state is ConfigEntryState.SETUP_RETRY
|
|
|
|
|
|
async def test_setup_legacy_domain_unique_id_multiple_eligible_fails(
|
|
hass: HomeAssistant,
|
|
mock_create_discovery: AsyncMock,
|
|
mock_discovery_service: Mock,
|
|
) -> None:
|
|
"""Legacy unique_id=DOMAIN with multiple eligible endpoints is SETUP_ERROR."""
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id=DOMAIN,
|
|
title="iZone Aircon",
|
|
data={},
|
|
version=2,
|
|
)
|
|
entry.add_to_hass(hass)
|
|
mock_discovery_service.discover_all = AsyncMock(
|
|
return_value=[
|
|
ControllerEndpoint(uid="000000001", host="192.0.2.1"),
|
|
ControllerEndpoint(uid="000000002", host="192.0.2.2"),
|
|
]
|
|
)
|
|
|
|
assert not await hass.config_entries.async_setup(entry.entry_id)
|
|
assert entry.state is ConfigEntryState.SETUP_ERROR
|
|
|
|
|
|
async def test_setup_heals_missing_host_via_discover_endpoint(
|
|
hass: HomeAssistant,
|
|
mock_create_discovery: AsyncMock,
|
|
mock_discovery_service: Mock,
|
|
mock_controller: Mock,
|
|
) -> None:
|
|
"""Real UID with empty data recovers CONF_HOST via discover_by_uid."""
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id="000000001",
|
|
data={},
|
|
version=2,
|
|
)
|
|
entry.add_to_hass(hass)
|
|
mock_discovery_service.discover_by_uid = AsyncMock(
|
|
return_value=ControllerEndpoint(uid="000000001", host="192.0.2.1")
|
|
)
|
|
|
|
assert await hass.config_entries.async_setup(entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
assert entry.state is ConfigEntryState.LOADED
|
|
assert entry.data[CONF_HOST] == "192.0.2.1"
|
|
mock_discovery_service.create_controller.assert_awaited_once()
|
|
|
|
|
|
async def test_setup_missing_host_not_found_retries(
|
|
hass: HomeAssistant,
|
|
mock_create_discovery: AsyncMock,
|
|
mock_discovery_service: Mock,
|
|
) -> None:
|
|
"""Real UID with empty data and no discovered endpoint leaves SETUP_RETRY."""
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id="000000001",
|
|
data={},
|
|
version=2,
|
|
)
|
|
entry.add_to_hass(hass)
|
|
mock_discovery_service.discover_by_uid = AsyncMock(return_value=None)
|
|
|
|
assert not await hass.config_entries.async_setup(entry.entry_id)
|
|
assert entry.state is ConfigEntryState.SETUP_RETRY
|
|
assert entry.error_reason_translation_key == "controller_not_found"
|
|
assert entry.error_reason_translation_placeholders == {"uid": "000000001"}
|
|
mock_discovery_service.create_controller.assert_not_awaited()
|
|
|
|
|
|
async def test_setup_legacy_domain_unique_id_discovery_oserror_retries(
|
|
hass: HomeAssistant,
|
|
mock_create_discovery: AsyncMock,
|
|
) -> None:
|
|
"""OSError while discovering for legacy DOMAIN unique_id leaves SETUP_RETRY."""
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id=DOMAIN,
|
|
title="iZone Aircon",
|
|
data={},
|
|
version=2,
|
|
)
|
|
entry.add_to_hass(hass)
|
|
|
|
with patch(
|
|
"homeassistant.components.izone.async_discover_all_endpoints",
|
|
new=AsyncMock(side_effect=OSError("bind failed")),
|
|
):
|
|
assert not await hass.config_entries.async_setup(entry.entry_id)
|
|
|
|
assert entry.state is ConfigEntryState.SETUP_RETRY
|
|
assert entry.error_reason_translation_key == "discovery_failed_legacy"
|
|
|
|
|
|
async def test_setup_missing_host_discover_oserror_retries(
|
|
hass: HomeAssistant,
|
|
mock_create_discovery: AsyncMock,
|
|
) -> None:
|
|
"""OSError while resolving host for a real UID leaves SETUP_RETRY."""
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id="000000001",
|
|
data={},
|
|
version=2,
|
|
)
|
|
entry.add_to_hass(hass)
|
|
|
|
with patch(
|
|
"homeassistant.components.izone.async_discover_endpoint",
|
|
new=AsyncMock(side_effect=OSError("bind failed")),
|
|
):
|
|
assert not await hass.config_entries.async_setup(entry.entry_id)
|
|
|
|
assert entry.state is ConfigEntryState.SETUP_RETRY
|
|
assert entry.error_reason_translation_key == "discovery_failed_host"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("side_effect", "expected_state", "translation_key", "placeholders"),
|
|
[
|
|
pytest.param(
|
|
ConnectionError("offline"),
|
|
ConfigEntryState.SETUP_RETRY,
|
|
"cannot_connect",
|
|
{"host": "192.0.2.1"},
|
|
id="connection-error",
|
|
),
|
|
pytest.param(
|
|
UnpairedBridgeError("unpaired"),
|
|
ConfigEntryState.SETUP_ERROR,
|
|
"bridge_unpaired",
|
|
None,
|
|
id="unpaired-bridge",
|
|
),
|
|
pytest.param(
|
|
ControllerCommandError("rejected"),
|
|
ConfigEntryState.SETUP_ERROR,
|
|
"setup_rejected",
|
|
{"host": "192.0.2.1"},
|
|
id="command-rejected",
|
|
),
|
|
],
|
|
)
|
|
async def test_setup_create_controller_errors(
|
|
hass: HomeAssistant,
|
|
mock_config_entry: MockConfigEntry,
|
|
mock_create_discovery: AsyncMock,
|
|
mock_discovery_service: Mock,
|
|
side_effect: Exception,
|
|
expected_state: ConfigEntryState,
|
|
translation_key: str,
|
|
placeholders: dict[str, str] | None,
|
|
) -> None:
|
|
"""create_controller failures map to retry vs error entry states."""
|
|
mock_config_entry.add_to_hass(hass)
|
|
mock_discovery_service.create_controller = AsyncMock(side_effect=side_effect)
|
|
|
|
assert not await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
|
assert mock_config_entry.state is expected_state
|
|
assert mock_config_entry.error_reason_translation_key == translation_key
|
|
assert mock_config_entry.error_reason_translation_placeholders == placeholders
|
|
|
|
|
|
async def test_setup_discovery_bind_failure_retries(
|
|
hass: HomeAssistant,
|
|
mock_config_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""UDP bind failure during discovery start leaves the entry in SETUP_RETRY."""
|
|
mock_config_entry.add_to_hass(hass)
|
|
|
|
with (
|
|
patch(
|
|
"homeassistant.components.izone.discovery.aiohttp_client.async_get_clientsession",
|
|
return_value=Mock(),
|
|
),
|
|
patch(
|
|
"homeassistant.components.izone.discovery.pizone.create_discovery",
|
|
new=AsyncMock(side_effect=OSError("bind failed")),
|
|
),
|
|
):
|
|
assert not await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
|
|
|
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
|
assert mock_config_entry.error_reason_translation_key == "discovery_start_failed"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"side_effect",
|
|
[
|
|
pytest.param(ConnectionError("gone"), id="connection-error"),
|
|
pytest.param(ControllerCommandError("rejected"), id="command-rejected"),
|
|
],
|
|
)
|
|
async def test_setup_first_refresh_failure_closes_controller(
|
|
hass: HomeAssistant,
|
|
mock_config_entry: MockConfigEntry,
|
|
mock_create_discovery: AsyncMock,
|
|
mock_controller: Mock,
|
|
side_effect: Exception,
|
|
) -> None:
|
|
"""A failed first refresh closes the controller and retries setup."""
|
|
mock_config_entry.add_to_hass(hass)
|
|
mock_controller.refresh_all = AsyncMock(side_effect=side_effect)
|
|
|
|
assert not await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
|
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
|
mock_controller.close.assert_awaited()
|
|
|
|
|
|
async def test_setup_platform_failure_closes_controller(
|
|
hass: HomeAssistant,
|
|
mock_config_entry: MockConfigEntry,
|
|
mock_create_discovery: AsyncMock,
|
|
mock_controller: Mock,
|
|
) -> None:
|
|
"""A failure during platform forward still releases the controller claim."""
|
|
mock_config_entry.add_to_hass(hass)
|
|
|
|
with patch(
|
|
"homeassistant.config_entries.ConfigEntries.async_forward_entry_setups",
|
|
side_effect=RuntimeError("platform boom"),
|
|
):
|
|
assert not await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
|
|
|
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
|
|
mock_controller.close.assert_awaited()
|
|
|
|
|
|
async def test_setup_cancelled_closes_controller(
|
|
hass: HomeAssistant,
|
|
mock_config_entry: MockConfigEntry,
|
|
mock_create_discovery: AsyncMock,
|
|
mock_controller: Mock,
|
|
) -> None:
|
|
"""Cancellation after create_controller still closes it to release the UID claim."""
|
|
mock_config_entry.add_to_hass(hass)
|
|
|
|
with patch(
|
|
"homeassistant.components.izone.IZoneCoordinator.async_config_entry_first_refresh",
|
|
side_effect=CancelledError,
|
|
):
|
|
assert not await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
|
|
|
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
|
|
mock_controller.close.assert_awaited()
|
|
|
|
|
|
async def test_address_changed_updates_config_entry_host(
|
|
hass: HomeAssistant,
|
|
mock_config_entry: MockConfigEntry,
|
|
mock_create_discovery: AsyncMock,
|
|
mock_discovery_service: Mock,
|
|
) -> None:
|
|
"""Library address-change callback persists the new host on the entry."""
|
|
mock_config_entry.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
on_address_changed = mock_discovery_service.create_controller.await_args.kwargs[
|
|
"on_address_changed"
|
|
]
|
|
on_address_changed(ControllerEndpoint(uid="000000001", host="192.0.2.99"))
|
|
await hass.async_block_till_done()
|
|
|
|
assert mock_config_entry.data[CONF_HOST] == "192.0.2.99"
|
|
|
|
|
|
async def test_address_changed_same_host_is_ignored(
|
|
hass: HomeAssistant,
|
|
mock_config_entry: MockConfigEntry,
|
|
mock_create_discovery: AsyncMock,
|
|
mock_discovery_service: Mock,
|
|
) -> None:
|
|
"""Address callback with an unchanged host does not rewrite entry data."""
|
|
mock_config_entry.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
original = dict(mock_config_entry.data)
|
|
on_address_changed = mock_discovery_service.create_controller.await_args.kwargs[
|
|
"on_address_changed"
|
|
]
|
|
on_address_changed(ControllerEndpoint(uid="000000001", host="192.0.2.1"))
|
|
await hass.async_block_till_done()
|
|
|
|
assert dict(mock_config_entry.data) == original
|
|
|
|
|
|
async def test_last_unload_stops_shared_discovery(
|
|
hass: HomeAssistant,
|
|
mock_create_discovery: AsyncMock,
|
|
mock_discovery_service: Mock,
|
|
) -> None:
|
|
"""Unloading the last loaded entry closes the shared discovery service."""
|
|
first = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id="000000001",
|
|
data={CONF_HOST: "192.0.2.1"},
|
|
entry_id="entry_1",
|
|
version=2,
|
|
)
|
|
second = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id="000000002",
|
|
data={CONF_HOST: "192.0.2.2"},
|
|
entry_id="entry_2",
|
|
version=2,
|
|
)
|
|
first_controller = create_mock_controller("000000001", "192.0.2.1")
|
|
second_controller = create_mock_controller("000000002", "192.0.2.2")
|
|
|
|
async def _create_controller(uid: str, host: str, **kwargs: object) -> Mock:
|
|
if uid == "000000001":
|
|
return first_controller
|
|
return second_controller
|
|
|
|
mock_discovery_service.create_controller = AsyncMock(side_effect=_create_controller)
|
|
|
|
first.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(first.entry_id)
|
|
await hass.async_block_till_done()
|
|
assert first.state is ConfigEntryState.LOADED
|
|
|
|
second.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(second.entry_id)
|
|
await hass.async_block_till_done()
|
|
assert second.state is ConfigEntryState.LOADED
|
|
|
|
mock_discovery_service.close.reset_mock()
|
|
assert await hass.config_entries.async_unload(first.entry_id)
|
|
await hass.async_block_till_done()
|
|
mock_discovery_service.close.assert_not_awaited()
|
|
|
|
assert await hass.config_entries.async_unload(second.entry_id)
|
|
await hass.async_block_till_done()
|
|
mock_discovery_service.close.assert_awaited_once()
|
|
|
|
|
|
async def test_setup_after_last_unload_recreates_discovery(
|
|
hass: HomeAssistant,
|
|
mock_config_entry: MockConfigEntry,
|
|
mock_create_discovery: AsyncMock,
|
|
mock_discovery_service: Mock,
|
|
mock_controller: Mock,
|
|
) -> None:
|
|
"""After the last unload stops discovery, a later setup starts it again."""
|
|
mock_config_entry.add_to_hass(hass)
|
|
|
|
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
mock_create_discovery.assert_awaited_once()
|
|
|
|
assert await hass.config_entries.async_unload(mock_config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
mock_discovery_service.close.assert_awaited_once()
|
|
|
|
mock_create_discovery.reset_mock()
|
|
mock_discovery_service.close.reset_mock()
|
|
mock_controller.close.reset_mock()
|
|
|
|
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
assert mock_config_entry.state is ConfigEntryState.LOADED
|
|
mock_create_discovery.assert_awaited_once()
|
|
mock_discovery_service.create_controller.assert_awaited()
|