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
This commit is contained in:
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: def create_config_entry_from_info(info: ReceiverInfo) -> MockConfigEntry:
"""Create a config entry from receiver info.""" """Create a config entry from receiver info."""
data = {CONF_HOST: info.host} data = {CONF_HOST: info.host}

View File

@ -1,25 +1,16 @@
"""Configure tests for the Onkyo integration.""" """Configure tests for the Onkyo integration."""
from collections.abc import Generator from unittest.mock import patch
from unittest.mock import AsyncMock, patch
import pytest import pytest
from homeassistant.components.onkyo.const import DOMAIN from homeassistant.components.onkyo.const import DOMAIN
from . import create_connection
from tests.common import MockConfigEntry 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") @pytest.fixture(name="config_entry")
def mock_config_entry() -> MockConfigEntry: def mock_config_entry() -> MockConfigEntry:
"""Create Onkyo entry in Home Assistant.""" """Create Onkyo entry in Home Assistant."""
@ -28,3 +19,56 @@ def mock_config_entry() -> MockConfigEntry:
title="Onkyo", title="Onkyo",
data={}, 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 ( from . import (
create_config_entry_from_info, create_config_entry_from_info,
create_connection,
create_empty_config_entry, create_empty_config_entry,
create_receiver_info, create_receiver_info,
setup_integration, setup_integration,
) )
from tests.common import Mock, MockConfigEntry from tests.common import MockConfigEntry
async def test_user_initial_menu(hass: HomeAssistant) -> None: 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"} 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.""" """Test valid host entered."""
init_result = await hass.config_entries.flow.async_init( init_result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": SOURCE_USER}, context={"source": SOURCE_USER},
@ -53,30 +53,17 @@ async def test_manual_valid_host(hass: HomeAssistant) -> None:
{"next_step_id": "manual"}, {"next_step_id": "manual"},
) )
mock_info = Mock() select_result = await hass.config_entries.flow.async_configure(
mock_info.identifier = "mock_id" form_result["flow_id"],
mock_info.host = "mock_host" user_input={CONF_HOST: "host 1"},
mock_info.model_name = "mock_model" )
with patch( assert select_result["step_id"] == "configure_receiver"
"homeassistant.components.onkyo.config_flow.async_interview", assert select_result["description_placeholders"]["name"] == "type 1 (host 1)"
return_value=mock_info,
):
select_result = await hass.config_entries.flow.async_configure(
form_result["flow_id"],
user_input={CONF_HOST: "sample-host-name"},
)
assert select_result["step_id"] == "configure_receiver"
assert (
select_result["description_placeholders"]["name"]
== "mock_model (mock_host)"
)
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.""" """Test invalid host entered."""
init_result = await hass.config_entries.flow.async_init( init_result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": SOURCE_USER}, context={"source": SOURCE_USER},
@ -87,19 +74,18 @@ async def test_manual_invalid_host(hass: HomeAssistant) -> None:
{"next_step_id": "manual"}, {"next_step_id": "manual"},
) )
with patch( host_result = await hass.config_entries.flow.async_configure(
"homeassistant.components.onkyo.config_flow.async_interview", return_value=None form_result["flow_id"],
): user_input={CONF_HOST: "sample-host-name"},
host_result = await hass.config_entries.flow.async_configure( )
form_result["flow_id"],
user_input={CONF_HOST: "sample-host-name"},
)
assert host_result["step_id"] == "manual" assert host_result["step_id"] == "manual"
assert host_result["errors"]["base"] == "cannot_connect" 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.""" """Test valid host entered."""
init_result = await hass.config_entries.flow.async_init( init_result = await hass.config_entries.flow.async_init(
@ -112,55 +98,49 @@ async def test_manual_valid_host_unexpected_error(hass: HomeAssistant) -> None:
{"next_step_id": "manual"}, {"next_step_id": "manual"},
) )
with patch( host_result = await hass.config_entries.flow.async_configure(
"homeassistant.components.onkyo.config_flow.async_interview", form_result["flow_id"],
side_effect=Exception(), user_input={CONF_HOST: "sample-host-name"},
): )
host_result = await hass.config_entries.flow.async_configure(
form_result["flow_id"],
user_input={CONF_HOST: "sample-host-name"},
)
assert host_result["step_id"] == "manual" assert host_result["step_id"] == "manual"
assert host_result["errors"]["base"] == "unknown" 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.""" """Test initial menu."""
init_result = await hass.config_entries.flow.async_init( init_result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": SOURCE_USER}, context={"source": SOURCE_USER},
) )
with patch( form_result = await hass.config_entries.flow.async_configure(
"homeassistant.components.onkyo.config_flow.async_discover", return_value=[] init_result["flow_id"],
): {"next_step_id": "eiscp_discovery"},
form_result = await hass.config_entries.flow.async_configure( )
init_result["flow_id"],
{"next_step_id": "eiscp_discovery"},
)
assert form_result["type"] is FlowResultType.ABORT assert form_result["type"] is FlowResultType.ABORT
assert form_result["reason"] == "no_devices_found" 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.""" """Test discovery which throws an unexpected exception."""
init_result = await hass.config_entries.flow.async_init( init_result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": SOURCE_USER}, 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"},
)
assert form_result["type"] is FlowResultType.ABORT form_result = await hass.config_entries.flow.async_configure(
assert form_result["reason"] == "unknown" init_result["flow_id"],
{"next_step_id": "eiscp_discovery"},
)
assert form_result["type"] is FlowResultType.ABORT
assert form_result["reason"] == "unknown"
async def test_discovery_with_new_and_existing_found(hass: HomeAssistant) -> None: async def test_discovery_with_new_and_existing_found(hass: HomeAssistant) -> None:
@ -170,13 +150,12 @@ async def test_discovery_with_new_and_existing_found(hass: HomeAssistant) -> Non
context={"source": SOURCE_USER}, 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 ( with (
patch( patch("pyeiscp.Connection.discover", new=mock_discover),
"homeassistant.components.onkyo.config_flow.async_discover",
return_value=infos,
),
# Fake it like the first entry was already added # Fake it like the first entry was already added
patch.object(OnkyoConfigFlow, "_async_current_ids", return_value=["id1"]), patch.object(OnkyoConfigFlow, "_async_current_ids", return_value=["id1"]),
): ):
@ -185,12 +164,12 @@ async def test_discovery_with_new_and_existing_found(hass: HomeAssistant) -> Non
{"next_step_id": "eiscp_discovery"}, {"next_step_id": "eiscp_discovery"},
) )
assert form_result["type"] is FlowResultType.FORM assert form_result["type"] is FlowResultType.FORM
assert form_result["data_schema"] is not None assert form_result["data_schema"] is not None
schema = form_result["data_schema"].schema schema = form_result["data_schema"].schema
container = schema["device"].container container = schema["device"].container
assert container == {"id2": "type 2 (host 2)"} assert container == {"id2": "type 2 (host 2)"}
async def test_discovery_with_one_selected(hass: HomeAssistant) -> None: async def test_discovery_with_one_selected(hass: HomeAssistant) -> None:
@ -200,14 +179,11 @@ async def test_discovery_with_one_selected(hass: HomeAssistant) -> None:
context={"source": SOURCE_USER}, 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 ( with patch("pyeiscp.Connection.discover", new=mock_discover):
patch(
"homeassistant.components.onkyo.config_flow.async_discover",
return_value=infos,
),
):
form_result = await hass.config_entries.flow.async_configure( form_result = await hass.config_entries.flow.async_configure(
init_result["flow_id"], init_result["flow_id"],
{"next_step_id": "eiscp_discovery"}, {"next_step_id": "eiscp_discovery"},
@ -218,11 +194,13 @@ async def test_discovery_with_one_selected(hass: HomeAssistant) -> None:
user_input={"device": "id42"}, user_input={"device": "id42"},
) )
assert select_result["step_id"] == "configure_receiver" assert select_result["step_id"] == "configure_receiver"
assert select_result["description_placeholders"]["name"] == "type 42 (host 42)" 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.""" """Test receiver configuration with no sources set."""
init_result = await hass.config_entries.flow.async_init( init_result = await hass.config_entries.flow.async_init(
@ -235,29 +213,22 @@ async def test_configure_empty_source_list(hass: HomeAssistant) -> None:
{"next_step_id": "manual"}, {"next_step_id": "manual"},
) )
mock_info = Mock() select_result = await hass.config_entries.flow.async_configure(
mock_info.identifier = "mock_id" form_result["flow_id"],
user_input={CONF_HOST: "sample-host-name"},
)
with patch( configure_result = await hass.config_entries.flow.async_configure(
"homeassistant.components.onkyo.config_flow.async_interview", select_result["flow_id"],
return_value=mock_info, user_input={"volume_resolution": 200, "input_sources": []},
): )
select_result = await hass.config_entries.flow.async_configure(
form_result["flow_id"],
user_input={CONF_HOST: "sample-host-name"},
)
configure_result = await hass.config_entries.flow.async_configure( assert configure_result["errors"] == {"input_sources": "empty_input_source_list"}
select_result["flow_id"],
user_input={"volume_resolution": 200, "input_sources": []},
)
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.""" """Test receiver configure with no resolution set."""
init_result = await hass.config_entries.flow.async_init( init_result = await hass.config_entries.flow.async_init(
@ -270,26 +241,21 @@ async def test_configure_no_resolution(hass: HomeAssistant) -> None:
{"next_step_id": "manual"}, {"next_step_id": "manual"},
) )
mock_info = Mock() select_result = await hass.config_entries.flow.async_configure(
mock_info.identifier = "mock_id" form_result["flow_id"],
user_input={CONF_HOST: "sample-host-name"},
)
with patch( with pytest.raises(InvalidData):
"homeassistant.components.onkyo.config_flow.async_interview", await hass.config_entries.flow.async_configure(
return_value=mock_info, select_result["flow_id"],
): user_input={"input_sources": ["TV"]},
select_result = await hass.config_entries.flow.async_configure(
form_result["flow_id"],
user_input={CONF_HOST: "sample-host-name"},
) )
with pytest.raises(InvalidData):
await hass.config_entries.flow.async_configure(
select_result["flow_id"],
user_input={"input_sources": ["TV"]},
)
async def test_configure_resolution_set(
async def test_configure_resolution_set(hass: HomeAssistant) -> None: hass: HomeAssistant, default_mock_discovery
) -> None:
"""Test receiver configure with specified resolution.""" """Test receiver configure with specified resolution."""
init_result = await hass.config_entries.flow.async_init( init_result = await hass.config_entries.flow.async_init(
@ -302,16 +268,10 @@ async def test_configure_resolution_set(hass: HomeAssistant) -> None:
{"next_step_id": "manual"}, {"next_step_id": "manual"},
) )
receiver_info = create_receiver_info(1) select_result = await hass.config_entries.flow.async_configure(
form_result["flow_id"],
with patch( user_input={CONF_HOST: "sample-host-name"},
"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"},
)
configure_result = await hass.config_entries.flow.async_configure( configure_result = await hass.config_entries.flow.async_configure(
select_result["flow_id"], select_result["flow_id"],
@ -322,7 +282,9 @@ async def test_configure_resolution_set(hass: HomeAssistant) -> None:
assert configure_result["options"]["volume_resolution"] == 200 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.""" """Test receiver configure with invalid resolution."""
init_result = await hass.config_entries.flow.async_init( init_result = await hass.config_entries.flow.async_init(
@ -335,26 +297,19 @@ async def test_configure_invalid_resolution_set(hass: HomeAssistant) -> None:
{"next_step_id": "manual"}, {"next_step_id": "manual"},
) )
mock_info = Mock() select_result = await hass.config_entries.flow.async_configure(
mock_info.identifier = "mock_id" form_result["flow_id"],
user_input={CONF_HOST: "sample-host-name"},
)
with patch( with pytest.raises(InvalidData):
"homeassistant.components.onkyo.config_flow.async_interview", await hass.config_entries.flow.async_configure(
return_value=mock_info, select_result["flow_id"],
): user_input={"volume_resolution": 42, "input_sources": ["TV"]},
select_result = await hass.config_entries.flow.async_configure(
form_result["flow_id"],
user_input={CONF_HOST: "sample-host-name"},
) )
with pytest.raises(InvalidData):
await hass.config_entries.flow.async_configure(
select_result["flow_id"],
user_input={"volume_resolution": 42, "input_sources": ["TV"]},
)
async def test_reconfigure(hass: HomeAssistant, default_mock_discovery) -> None:
async def test_reconfigure(hass: HomeAssistant) -> None:
"""Test the reconfigure config flow.""" """Test the reconfigure config flow."""
receiver_info = create_receiver_info(1) receiver_info = create_receiver_info(1)
config_entry = create_config_entry_from_info(receiver_info) config_entry = create_config_entry_from_info(receiver_info)
@ -368,14 +323,10 @@ async def test_reconfigure(hass: HomeAssistant) -> None:
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "manual" assert result["step_id"] == "manual"
with patch( result2 = await hass.config_entries.flow.async_configure(
"homeassistant.components.onkyo.config_flow.async_interview", result["flow_id"], user_input={"host": receiver_info.host}
return_value=receiver_info, )
): await hass.async_block_till_done()
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={"host": receiver_info.host}
)
await hass.async_block_till_done()
assert result2["type"] is FlowResultType.FORM assert result2["type"] is FlowResultType.FORM
assert result2["step_id"] == "configure_receiver" assert result2["step_id"] == "configure_receiver"
@ -403,14 +354,18 @@ async def test_reconfigure_new_device(hass: HomeAssistant) -> None:
result = await config_entry.start_reconfigure_flow(hass) 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( with patch(
"homeassistant.components.onkyo.config_flow.async_interview", "homeassistant.components.onkyo.receiver.pyeiscp.Connection.discover",
return_value=receiver_info_2, new=mock_discover,
): ):
result2 = await hass.config_entries.flow.async_configure( 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() await hass.async_block_till_done()
@ -455,12 +410,10 @@ async def test_import_fail(
error: str, error: str,
) -> None: ) -> None:
"""Test import flow failed.""" """Test import flow failed."""
with (
patch( with patch(
"homeassistant.components.onkyo.config_flow.async_interview", "homeassistant.components.onkyo.receiver.pyeiscp.Connection.discover",
return_value=None, side_effect=exception,
side_effect=exception,
),
): ):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=user_input DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=user_input