From 52c8c80f91283dd2665cd242d33131d98dfbcb17 Mon Sep 17 00:00:00 2001 From: Chris McCurdy Date: Wed, 7 Sep 2022 11:43:05 -0400 Subject: [PATCH] Add additional method of retrieving UUID for LG soundbar configuration (#77856) --- .../components/lg_soundbar/config_flow.py | 19 +- .../lg_soundbar/test_config_flow.py | 168 +++++++++++++++++- 2 files changed, 182 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/lg_soundbar/config_flow.py b/homeassistant/components/lg_soundbar/config_flow.py index bd9a727d1f4..0606bad2d67 100644 --- a/homeassistant/components/lg_soundbar/config_flow.py +++ b/homeassistant/components/lg_soundbar/config_flow.py @@ -1,5 +1,5 @@ """Config flow to configure the LG Soundbar integration.""" -from queue import Queue +from queue import Full, Queue import socket import temescal @@ -20,18 +20,29 @@ def test_connect(host, port): uuid_q = Queue(maxsize=1) name_q = Queue(maxsize=1) + def queue_add(attr_q, data): + try: + attr_q.put_nowait(data) + except Full: + pass + def msg_callback(response): - if response["msg"] == "MAC_INFO_DEV" and "s_uuid" in response["data"]: - uuid_q.put_nowait(response["data"]["s_uuid"]) + if ( + response["msg"] in ["MAC_INFO_DEV", "PRODUCT_INFO"] + and "s_uuid" in response["data"] + ): + queue_add(uuid_q, response["data"]["s_uuid"]) if ( response["msg"] == "SPK_LIST_VIEW_INFO" and "s_user_name" in response["data"] ): - name_q.put_nowait(response["data"]["s_user_name"]) + queue_add(name_q, response["data"]["s_user_name"]) try: connection = temescal.temescal(host, port=port, callback=msg_callback) connection.get_mac_info() + if uuid_q.empty(): + connection.get_product_info() connection.get_info() details = {"name": name_q.get(timeout=10), "uuid": uuid_q.get(timeout=10)} return details diff --git a/tests/components/lg_soundbar/test_config_flow.py b/tests/components/lg_soundbar/test_config_flow.py index 3fafc2c7628..8bcf817cbba 100644 --- a/tests/components/lg_soundbar/test_config_flow.py +++ b/tests/components/lg_soundbar/test_config_flow.py @@ -1,5 +1,5 @@ """Test the lg_soundbar config flow.""" -from unittest.mock import MagicMock, patch +from unittest.mock import DEFAULT, MagicMock, Mock, call, patch from homeassistant import config_entries from homeassistant.components.lg_soundbar.const import DEFAULT_PORT, DOMAIN @@ -43,6 +43,172 @@ async def test_form(hass): assert len(mock_setup_entry.mock_calls) == 1 +async def test_form_uuid_missing_from_mac_info(hass): + """Test we get the form, but uuid is missing from the initial get_mac_info function call.""" + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + with patch( + "homeassistant.components.lg_soundbar.config_flow.temescal", return_value=Mock() + ) as mock_temescal, patch( + "homeassistant.components.lg_soundbar.async_setup_entry", return_value=True + ) as mock_setup_entry: + tmock = mock_temescal.temescal + tmock.return_value = Mock() + instance = tmock.return_value + + def temescal_side_effect(addr, port, callback): + product_info = {"msg": "PRODUCT_INFO", "data": {"s_uuid": "uuid"}} + instance.get_product_info.side_effect = lambda: callback(product_info) + info = {"msg": "SPK_LIST_VIEW_INFO", "data": {"s_user_name": "name"}} + instance.get_info.side_effect = lambda: callback(info) + return DEFAULT + + tmock.side_effect = temescal_side_effect + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_HOST: "1.1.1.1", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == "create_entry" + assert result2["title"] == "name" + assert result2["data"] == { + CONF_HOST: "1.1.1.1", + CONF_PORT: DEFAULT_PORT, + } + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_uuid_present_in_both_functions_uuid_q_empty(hass): + """Get the form, uuid present in both get_mac_info and get_product_info calls. + + Value from get_mac_info is not added to uuid_q before get_product_info is run. + """ + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + mock_uuid_q = MagicMock() + mock_name_q = MagicMock() + + with patch( + "homeassistant.components.lg_soundbar.config_flow.temescal", return_value=Mock() + ) as mock_temescal, patch( + "homeassistant.components.lg_soundbar.config_flow.Queue", + return_value=MagicMock(), + ) as mock_q, patch( + "homeassistant.components.lg_soundbar.async_setup_entry", return_value=True + ) as mock_setup_entry: + mock_q.side_effect = [mock_uuid_q, mock_name_q] + mock_uuid_q.empty.return_value = True + mock_uuid_q.get.return_value = "uuid" + mock_name_q.get.return_value = "name" + tmock = mock_temescal.temescal + tmock.return_value = Mock() + instance = tmock.return_value + + def temescal_side_effect(addr, port, callback): + mac_info = {"msg": "MAC_INFO_DEV", "data": {"s_uuid": "uuid"}} + instance.get_mac_info.side_effect = lambda: callback(mac_info) + product_info = {"msg": "PRODUCT_INFO", "data": {"s_uuid": "uuid"}} + instance.get_product_info.side_effect = lambda: callback(product_info) + info = {"msg": "SPK_LIST_VIEW_INFO", "data": {"s_user_name": "name"}} + instance.get_info.side_effect = lambda: callback(info) + return DEFAULT + + tmock.side_effect = temescal_side_effect + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_HOST: "1.1.1.1", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == "create_entry" + assert result2["title"] == "name" + assert result2["data"] == { + CONF_HOST: "1.1.1.1", + CONF_PORT: DEFAULT_PORT, + } + assert len(mock_setup_entry.mock_calls) == 1 + mock_uuid_q.empty.assert_called_once() + mock_uuid_q.put_nowait.has_calls([call("uuid"), call("uuid")]) + mock_uuid_q.get.assert_called_once() + + +async def test_form_uuid_present_in_both_functions_uuid_q_not_empty(hass): + """Get the form, uuid present in both get_mac_info and get_product_info calls. + + Value from get_mac_info is added to uuid_q before get_product_info is run. + """ + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + mock_uuid_q = MagicMock() + mock_name_q = MagicMock() + + with patch( + "homeassistant.components.lg_soundbar.config_flow.temescal", return_value=Mock() + ) as mock_temescal, patch( + "homeassistant.components.lg_soundbar.config_flow.Queue", + return_value=MagicMock(), + ) as mock_q, patch( + "homeassistant.components.lg_soundbar.async_setup_entry", return_value=True + ) as mock_setup_entry: + mock_q.side_effect = [mock_uuid_q, mock_name_q] + mock_uuid_q.empty.return_value = False + mock_uuid_q.get.return_value = "uuid" + mock_name_q.get.return_value = "name" + tmock = mock_temescal.temescal + tmock.return_value = Mock() + instance = tmock.return_value + + def temescal_side_effect(addr, port, callback): + mac_info = {"msg": "MAC_INFO_DEV", "data": {"s_uuid": "uuid"}} + instance.get_mac_info.side_effect = lambda: callback(mac_info) + info = {"msg": "SPK_LIST_VIEW_INFO", "data": {"s_user_name": "name"}} + instance.get_info.side_effect = lambda: callback(info) + return DEFAULT + + tmock.side_effect = temescal_side_effect + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_HOST: "1.1.1.1", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == "create_entry" + assert result2["title"] == "name" + assert result2["data"] == { + CONF_HOST: "1.1.1.1", + CONF_PORT: DEFAULT_PORT, + } + assert len(mock_setup_entry.mock_calls) == 1 + mock_uuid_q.empty.assert_called_once() + mock_uuid_q.put_nowait.assert_called_once() + mock_uuid_q.get.assert_called_once() + + async def test_form_cannot_connect(hass): """Test we handle cannot connect error.""" result = await hass.config_entries.flow.async_init(