Refactor Onkyo tests to patch underlying pyeiscp library (#132653)

* Refactor Onkyo tests to patch underlying pyeiscp library instead of home assistant methods

* limit test patches to specific component, move atches into conftest

* use patch.multiple and restrict patches to specific component

* use side effect instead of mocking method
pull/133304/head
Tomer Shemesh 2024-12-15 12:27:17 -05:00 committed by GitHub
parent f069f340a3
commit 2a49378f4c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 179 additions and 172 deletions

View File

@ -19,6 +19,16 @@ def create_receiver_info(id: int) -> ReceiverInfo:
)
def create_connection(id: int) -> Mock:
"""Create an mock connection object for testing."""
connection = Mock()
connection.host = f"host {id}"
connection.port = 0
connection.name = f"type {id}"
connection.identifier = f"id{id}"
return connection
def create_config_entry_from_info(info: ReceiverInfo) -> MockConfigEntry:
"""Create a config entry from receiver info."""
data = {CONF_HOST: info.host}

View File

@ -1,25 +1,16 @@
"""Configure tests for the Onkyo integration."""
from collections.abc import Generator
from unittest.mock import AsyncMock, patch
from unittest.mock import patch
import pytest
from homeassistant.components.onkyo.const import DOMAIN
from . import create_connection
from tests.common import MockConfigEntry
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock]:
"""Override async_setup_entry."""
with patch(
"homeassistant.components.onkyo.async_setup_entry",
return_value=True,
) as mock_setup_entry:
yield mock_setup_entry
@pytest.fixture(name="config_entry")
def mock_config_entry() -> MockConfigEntry:
"""Create Onkyo entry in Home Assistant."""
@ -28,3 +19,56 @@ def mock_config_entry() -> MockConfigEntry:
title="Onkyo",
data={},
)
@pytest.fixture(autouse=True)
def patch_timeouts():
"""Patch timeouts to avoid tests waiting."""
with patch.multiple(
"homeassistant.components.onkyo.receiver",
DEVICE_INTERVIEW_TIMEOUT=0,
DEVICE_DISCOVERY_TIMEOUT=0,
):
yield
@pytest.fixture
async def default_mock_discovery():
"""Mock discovery with a single device."""
async def mock_discover(host=None, discovery_callback=None, timeout=0):
await discovery_callback(create_connection(1))
with patch(
"homeassistant.components.onkyo.receiver.pyeiscp.Connection.discover",
new=mock_discover,
):
yield
@pytest.fixture
async def stub_mock_discovery():
"""Mock discovery with no devices."""
async def mock_discover(host=None, discovery_callback=None, timeout=0):
pass
with patch(
"homeassistant.components.onkyo.receiver.pyeiscp.Connection.discover",
new=mock_discover,
):
yield
@pytest.fixture
async def empty_mock_discovery():
"""Mock discovery with an empty connection."""
async def mock_discover(host=None, discovery_callback=None, timeout=0):
await discovery_callback(None)
with patch(
"homeassistant.components.onkyo.receiver.pyeiscp.Connection.discover",
new=mock_discover,
):
yield

View File

@ -20,12 +20,13 @@ from homeassistant.data_entry_flow import FlowResultType, InvalidData
from . import (
create_config_entry_from_info,
create_connection,
create_empty_config_entry,
create_receiver_info,
setup_integration,
)
from tests.common import Mock, MockConfigEntry
from tests.common import MockConfigEntry
async def test_user_initial_menu(hass: HomeAssistant) -> None:
@ -40,9 +41,8 @@ async def test_user_initial_menu(hass: HomeAssistant) -> None:
assert not set(init_result["menu_options"]) ^ {"manual", "eiscp_discovery"}
async def test_manual_valid_host(hass: HomeAssistant) -> None:
async def test_manual_valid_host(hass: HomeAssistant, default_mock_discovery) -> None:
"""Test valid host entered."""
init_result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
@ -53,30 +53,17 @@ async def test_manual_valid_host(hass: HomeAssistant) -> None:
{"next_step_id": "manual"},
)
mock_info = Mock()
mock_info.identifier = "mock_id"
mock_info.host = "mock_host"
mock_info.model_name = "mock_model"
with patch(
"homeassistant.components.onkyo.config_flow.async_interview",
return_value=mock_info,
):
select_result = await hass.config_entries.flow.async_configure(
form_result["flow_id"],
user_input={CONF_HOST: "sample-host-name"},
user_input={CONF_HOST: "host 1"},
)
assert select_result["step_id"] == "configure_receiver"
assert (
select_result["description_placeholders"]["name"]
== "mock_model (mock_host)"
)
assert select_result["description_placeholders"]["name"] == "type 1 (host 1)"
async def test_manual_invalid_host(hass: HomeAssistant) -> None:
async def test_manual_invalid_host(hass: HomeAssistant, stub_mock_discovery) -> None:
"""Test invalid host entered."""
init_result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
@ -87,9 +74,6 @@ async def test_manual_invalid_host(hass: HomeAssistant) -> None:
{"next_step_id": "manual"},
)
with patch(
"homeassistant.components.onkyo.config_flow.async_interview", return_value=None
):
host_result = await hass.config_entries.flow.async_configure(
form_result["flow_id"],
user_input={CONF_HOST: "sample-host-name"},
@ -99,7 +83,9 @@ async def test_manual_invalid_host(hass: HomeAssistant) -> None:
assert host_result["errors"]["base"] == "cannot_connect"
async def test_manual_valid_host_unexpected_error(hass: HomeAssistant) -> None:
async def test_manual_valid_host_unexpected_error(
hass: HomeAssistant, empty_mock_discovery
) -> None:
"""Test valid host entered."""
init_result = await hass.config_entries.flow.async_init(
@ -112,10 +98,6 @@ async def test_manual_valid_host_unexpected_error(hass: HomeAssistant) -> None:
{"next_step_id": "manual"},
)
with patch(
"homeassistant.components.onkyo.config_flow.async_interview",
side_effect=Exception(),
):
host_result = await hass.config_entries.flow.async_configure(
form_result["flow_id"],
user_input={CONF_HOST: "sample-host-name"},
@ -125,16 +107,15 @@ async def test_manual_valid_host_unexpected_error(hass: HomeAssistant) -> None:
assert host_result["errors"]["base"] == "unknown"
async def test_discovery_and_no_devices_discovered(hass: HomeAssistant) -> None:
async def test_discovery_and_no_devices_discovered(
hass: HomeAssistant, stub_mock_discovery
) -> None:
"""Test initial menu."""
init_result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
)
with patch(
"homeassistant.components.onkyo.config_flow.async_discover", return_value=[]
):
form_result = await hass.config_entries.flow.async_configure(
init_result["flow_id"],
{"next_step_id": "eiscp_discovery"},
@ -144,16 +125,15 @@ async def test_discovery_and_no_devices_discovered(hass: HomeAssistant) -> None:
assert form_result["reason"] == "no_devices_found"
async def test_discovery_with_exception(hass: HomeAssistant) -> None:
async def test_discovery_with_exception(
hass: HomeAssistant, empty_mock_discovery
) -> None:
"""Test discovery which throws an unexpected exception."""
init_result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
)
with patch(
"homeassistant.components.onkyo.config_flow.async_discover",
side_effect=Exception(),
):
form_result = await hass.config_entries.flow.async_configure(
init_result["flow_id"],
{"next_step_id": "eiscp_discovery"},
@ -170,13 +150,12 @@ async def test_discovery_with_new_and_existing_found(hass: HomeAssistant) -> Non
context={"source": SOURCE_USER},
)
infos = [create_receiver_info(1), create_receiver_info(2)]
async def mock_discover(discovery_callback, timeout):
await discovery_callback(create_connection(1))
await discovery_callback(create_connection(2))
with (
patch(
"homeassistant.components.onkyo.config_flow.async_discover",
return_value=infos,
),
patch("pyeiscp.Connection.discover", new=mock_discover),
# Fake it like the first entry was already added
patch.object(OnkyoConfigFlow, "_async_current_ids", return_value=["id1"]),
):
@ -200,14 +179,11 @@ async def test_discovery_with_one_selected(hass: HomeAssistant) -> None:
context={"source": SOURCE_USER},
)
infos = [create_receiver_info(42), create_receiver_info(0)]
async def mock_discover(discovery_callback, timeout):
await discovery_callback(create_connection(42))
await discovery_callback(create_connection(0))
with (
patch(
"homeassistant.components.onkyo.config_flow.async_discover",
return_value=infos,
),
):
with patch("pyeiscp.Connection.discover", new=mock_discover):
form_result = await hass.config_entries.flow.async_configure(
init_result["flow_id"],
{"next_step_id": "eiscp_discovery"},
@ -222,7 +198,9 @@ async def test_discovery_with_one_selected(hass: HomeAssistant) -> None:
assert select_result["description_placeholders"]["name"] == "type 42 (host 42)"
async def test_configure_empty_source_list(hass: HomeAssistant) -> None:
async def test_configure_empty_source_list(
hass: HomeAssistant, default_mock_discovery
) -> None:
"""Test receiver configuration with no sources set."""
init_result = await hass.config_entries.flow.async_init(
@ -235,13 +213,6 @@ async def test_configure_empty_source_list(hass: HomeAssistant) -> None:
{"next_step_id": "manual"},
)
mock_info = Mock()
mock_info.identifier = "mock_id"
with patch(
"homeassistant.components.onkyo.config_flow.async_interview",
return_value=mock_info,
):
select_result = await hass.config_entries.flow.async_configure(
form_result["flow_id"],
user_input={CONF_HOST: "sample-host-name"},
@ -252,12 +223,12 @@ async def test_configure_empty_source_list(hass: HomeAssistant) -> None:
user_input={"volume_resolution": 200, "input_sources": []},
)
assert configure_result["errors"] == {
"input_sources": "empty_input_source_list"
}
assert configure_result["errors"] == {"input_sources": "empty_input_source_list"}
async def test_configure_no_resolution(hass: HomeAssistant) -> None:
async def test_configure_no_resolution(
hass: HomeAssistant, default_mock_discovery
) -> None:
"""Test receiver configure with no resolution set."""
init_result = await hass.config_entries.flow.async_init(
@ -270,13 +241,6 @@ async def test_configure_no_resolution(hass: HomeAssistant) -> None:
{"next_step_id": "manual"},
)
mock_info = Mock()
mock_info.identifier = "mock_id"
with patch(
"homeassistant.components.onkyo.config_flow.async_interview",
return_value=mock_info,
):
select_result = await hass.config_entries.flow.async_configure(
form_result["flow_id"],
user_input={CONF_HOST: "sample-host-name"},
@ -289,7 +253,9 @@ async def test_configure_no_resolution(hass: HomeAssistant) -> None:
)
async def test_configure_resolution_set(hass: HomeAssistant) -> None:
async def test_configure_resolution_set(
hass: HomeAssistant, default_mock_discovery
) -> None:
"""Test receiver configure with specified resolution."""
init_result = await hass.config_entries.flow.async_init(
@ -302,12 +268,6 @@ async def test_configure_resolution_set(hass: HomeAssistant) -> None:
{"next_step_id": "manual"},
)
receiver_info = create_receiver_info(1)
with patch(
"homeassistant.components.onkyo.config_flow.async_interview",
return_value=receiver_info,
):
select_result = await hass.config_entries.flow.async_configure(
form_result["flow_id"],
user_input={CONF_HOST: "sample-host-name"},
@ -322,7 +282,9 @@ async def test_configure_resolution_set(hass: HomeAssistant) -> None:
assert configure_result["options"]["volume_resolution"] == 200
async def test_configure_invalid_resolution_set(hass: HomeAssistant) -> None:
async def test_configure_invalid_resolution_set(
hass: HomeAssistant, default_mock_discovery
) -> None:
"""Test receiver configure with invalid resolution."""
init_result = await hass.config_entries.flow.async_init(
@ -335,13 +297,6 @@ async def test_configure_invalid_resolution_set(hass: HomeAssistant) -> None:
{"next_step_id": "manual"},
)
mock_info = Mock()
mock_info.identifier = "mock_id"
with patch(
"homeassistant.components.onkyo.config_flow.async_interview",
return_value=mock_info,
):
select_result = await hass.config_entries.flow.async_configure(
form_result["flow_id"],
user_input={CONF_HOST: "sample-host-name"},
@ -354,7 +309,7 @@ async def test_configure_invalid_resolution_set(hass: HomeAssistant) -> None:
)
async def test_reconfigure(hass: HomeAssistant) -> None:
async def test_reconfigure(hass: HomeAssistant, default_mock_discovery) -> None:
"""Test the reconfigure config flow."""
receiver_info = create_receiver_info(1)
config_entry = create_config_entry_from_info(receiver_info)
@ -368,10 +323,6 @@ async def test_reconfigure(hass: HomeAssistant) -> None:
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "manual"
with patch(
"homeassistant.components.onkyo.config_flow.async_interview",
return_value=receiver_info,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={"host": receiver_info.host}
)
@ -403,14 +354,18 @@ async def test_reconfigure_new_device(hass: HomeAssistant) -> None:
result = await config_entry.start_reconfigure_flow(hass)
receiver_info_2 = create_receiver_info(2)
mock_connection = create_connection(2)
# Create mock discover that calls callback immediately
async def mock_discover(host, discovery_callback, timeout):
await discovery_callback(mock_connection)
with patch(
"homeassistant.components.onkyo.config_flow.async_interview",
return_value=receiver_info_2,
"homeassistant.components.onkyo.receiver.pyeiscp.Connection.discover",
new=mock_discover,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={"host": receiver_info_2.host}
result["flow_id"], user_input={"host": mock_connection.host}
)
await hass.async_block_till_done()
@ -455,12 +410,10 @@ async def test_import_fail(
error: str,
) -> None:
"""Test import flow failed."""
with (
patch(
"homeassistant.components.onkyo.config_flow.async_interview",
return_value=None,
with patch(
"homeassistant.components.onkyo.receiver.pyeiscp.Connection.discover",
side_effect=exception,
),
):
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=user_input