async def test_scan_match_upnp_devicedesc(hass, aioclient_mock, key): """Test matching based on UPnP device description data.""" aioclient_mock.get( "http://1.1.1.1", text=f""" <root> <device> <{key}>Paulus</{key}> </device> </root> """, ) scanner = ssdp.Scanner(hass, {"mock-domain": [{key: "Paulus"}]}) with patch( "netdisco.ssdp.scan", return_value=[ Mock(st="mock-st", location="http://1.1.1.1", values={}) ], ), patch.object(hass.config_entries.flow, "async_init", return_value=mock_coro()) as mock_init: await scanner.async_scan(None) assert len(mock_init.mock_calls) == 1 assert mock_init.mock_calls[0][1][0] == "mock-domain" assert mock_init.mock_calls[0][2]["context"] == {"source": "ssdp"}
async def test_scan_match_device_type(hass, aioclient_mock): """Test matching based on ST.""" aioclient_mock.get('http://1.1.1.1', text=""" <root> <device> <deviceType>Paulus</deviceType> </device> </root> """) scanner = ssdp.Scanner(hass) with patch('netdisco.ssdp.scan', return_value=[ Mock(st="mock-st", location='http://1.1.1.1') ]), patch.dict(gn_ssdp.SSDP['device_type'], {'Paulus': ['mock-domain']}), patch.object( hass.config_entries.flow, 'async_init', return_value=mock_coro()) as mock_init: await scanner.async_scan(None) assert len(mock_init.mock_calls) == 1 assert mock_init.mock_calls[0][1][0] == 'mock-domain' assert mock_init.mock_calls[0][2]['context'] == {'source': 'ssdp'}
async def test_scan_match_upnp_devicedesc(hass, aioclient_mock, key): """Test matching based on UPnP device description data.""" aioclient_mock.get( "http://1.1.1.1", text=f""" <root> <device> <{key}>Paulus</{key}> </device> </root> """, ) scanner = ssdp.Scanner(hass, {"mock-domain": [{key: "Paulus"}]}) async def _inject_entry(*args, **kwargs): scanner.async_store_entry( Mock(st="mock-st", location="http://1.1.1.1", values={}) ) with patch( "homeassistant.components.ssdp.async_search", side_effect=_inject_entry, ), patch.object( hass.config_entries.flow, "async_init", return_value=mock_coro() ) as mock_init: await scanner.async_scan(None) assert len(mock_init.mock_calls) == 1 assert mock_init.mock_calls[0][1][0] == "mock-domain" assert mock_init.mock_calls[0][2]["context"] == {"source": "ssdp"}
async def test_scan_match_device_type(hass, aioclient_mock): """Test matching based on ST.""" aioclient_mock.get( "http://1.1.1.1", text=""" <root> <device> <deviceType>Paulus</deviceType> </device> </root> """, ) scanner = ssdp.Scanner(hass) with patch( "netdisco.ssdp.scan", return_value=[Mock(st="mock-st", location="http://1.1.1.1")], ), patch.dict(gn_ssdp.SSDP["device_type"], {"Paulus": ["mock-domain"]}), patch.object( hass.config_entries.flow, "async_init", return_value=mock_coro()) as mock_init: await scanner.async_scan(None) assert len(mock_init.mock_calls) == 1 assert mock_init.mock_calls[0][1][0] == "mock-domain" assert mock_init.mock_calls[0][2]["context"] == {"source": "ssdp"}
async def test_scan_not_all_match(hass, aioclient_mock): """Test match fails if some specified attribute values differ.""" aioclient_mock.get( "http://1.1.1.1", text=f""" <root> <device> <deviceType>Paulus</deviceType> <manufacturer>Paulus</manufacturer> </device> </root> """, ) scanner = ssdp.Scanner(hass) with patch( "netdisco.ssdp.scan", return_value=[Mock(st="mock-st", location="http://1.1.1.1")], ), patch.dict( gn_ssdp.SSDP, {"mock-domain": [{"deviceType": "Paulus", "manufacturer": "Not-Paulus"}]}, ), patch.object( hass.config_entries.flow, "async_init", return_value=mock_coro() ) as mock_init: await scanner.async_scan(None) assert not mock_init.mock_calls
async def test_scan_match_st(hass, caplog): """Test matching based on ST.""" scanner = ssdp.Scanner(hass, {"mock-domain": [{"st": "mock-st"}]}) with patch( "netdisco.ssdp.scan", return_value=[ Mock( st="mock-st", location=None, values={ "usn": "mock-usn", "server": "mock-server", "ext": "" }, ) ], ), patch.object(hass.config_entries.flow, "async_init", return_value=mock_coro()) as mock_init: await scanner.async_scan(None) assert len(mock_init.mock_calls) == 1 assert mock_init.mock_calls[0][1][0] == "mock-domain" assert mock_init.mock_calls[0][2]["context"] == {"source": "ssdp"} assert mock_init.mock_calls[0][2]["data"] == { ssdp.ATTR_SSDP_ST: "mock-st", ssdp.ATTR_SSDP_LOCATION: None, ssdp.ATTR_SSDP_USN: "mock-usn", ssdp.ATTR_SSDP_SERVER: "mock-server", ssdp.ATTR_SSDP_EXT: "", } assert "Failed to fetch ssdp data" not in caplog.text
async def test_scan_not_all_present(hass, aioclient_mock): """Test match fails if some specified attributes are not present.""" aioclient_mock.get( "http://1.1.1.1", text=""" <root> <device> <deviceType>Paulus</deviceType> </device> </root> """, ) scanner = ssdp.Scanner( hass, { "mock-domain": [{ ssdp.ATTR_UPNP_DEVICE_TYPE: "Paulus", ssdp.ATTR_UPNP_MANUFACTURER: "Paulus", }] }, ) with patch( "netdisco.ssdp.scan", return_value=[ Mock(st="mock-st", location="http://1.1.1.1", values={}) ], ), patch.object(hass.config_entries.flow, "async_init", return_value=mock_coro()) as mock_init: await scanner.async_scan(None) assert not mock_init.mock_calls
async def test_scan_description_fetch_fail(hass, aioclient_mock, exc): """Test failing to fetch description.""" aioclient_mock.get('http://1.1.1.1', exc=exc) scanner = ssdp.Scanner(hass) with patch('netdisco.ssdp.scan', return_value=[Mock(st="mock-st", location='http://1.1.1.1')]): await scanner.async_scan(None)
async def test_scan_description_fetch_fail(hass, aioclient_mock, exc): """Test failing to fetch description.""" aioclient_mock.get("http://1.1.1.1", exc=exc) scanner = ssdp.Scanner(hass, {}) with patch( "netdisco.ssdp.scan", return_value=[Mock(st="mock-st", location="http://1.1.1.1", values={})], ): await scanner.async_scan(None)
async def test_scan_description_parse_fail(hass, aioclient_mock): """Test invalid XML.""" aioclient_mock.get('http://1.1.1.1', text=""" <root>INVALIDXML """) scanner = ssdp.Scanner(hass) with patch('netdisco.ssdp.scan', return_value=[Mock(st="mock-st", location='http://1.1.1.1')]): await scanner.async_scan(None)
async def test_invalid_characters(hass, aioclient_mock): """Test that we replace bad characters with placeholders.""" aioclient_mock.get( "http://1.1.1.1", text=""" <root> <device> <deviceType>ABC</deviceType> <serialNumber>\xff\xff\xff\xff</serialNumber> </device> </root> """, ) scanner = ssdp.Scanner( hass, { "mock-domain": [ { ssdp.ATTR_UPNP_DEVICE_TYPE: "ABC", } ] }, ) async def _mock_async_scan(*args, async_callback=None, **kwargs): await async_callback( { "st": "mock-st", "location": "http://1.1.1.1", } ) with patch( "homeassistant.components.ssdp.async_search", side_effect=_mock_async_scan, ), patch.object( hass.config_entries.flow, "async_init", return_value=mock_coro() ) as mock_init: await scanner.async_scan(None) assert len(mock_init.mock_calls) == 1 assert mock_init.mock_calls[0][1][0] == "mock-domain" assert mock_init.mock_calls[0][2]["context"] == { "source": config_entries.SOURCE_SSDP } assert mock_init.mock_calls[0][2]["data"] == { "ssdp_location": "http://1.1.1.1", "ssdp_st": "mock-st", "deviceType": "ABC", "serialNumber": "ÿÿÿÿ", }
async def test_scan_match_st(hass): """Test matching based on ST.""" scanner = ssdp.Scanner(hass) with patch( "netdisco.ssdp.scan", return_value=[Mock(st="mock-st", location=None)] ), patch.dict(gn_ssdp.SSDP, {"mock-domain": [{"st": "mock-st"}]}), patch.object( hass.config_entries.flow, "async_init", return_value=mock_coro() ) as mock_init: await scanner.async_scan(None) assert len(mock_init.mock_calls) == 1 assert mock_init.mock_calls[0][1][0] == "mock-domain" assert mock_init.mock_calls[0][2]["context"] == {"source": "ssdp"}
async def test_scan_description_fetch_fail(hass, aioclient_mock, exc): """Test failing to fetch description.""" aioclient_mock.get("http://1.1.1.1", exc=exc) scanner = ssdp.Scanner(hass, {}) async def _inject_entry(*args, **kwargs): scanner.async_store_entry( Mock(st="mock-st", location="http://1.1.1.1", values={}) ) with patch( "homeassistant.components.ssdp.async_search", side_effect=_inject_entry, ): await scanner.async_scan(None)
async def test_scan_description_parse_fail(hass, aioclient_mock): """Test invalid XML.""" aioclient_mock.get( "http://1.1.1.1", text=""" <root>INVALIDXML """, ) scanner = ssdp.Scanner(hass, {}) with patch( "netdisco.ssdp.scan", return_value=[Mock(st="mock-st", location="http://1.1.1.1", values={})], ): await scanner.async_scan(None)
async def test_scan_match_st(hass): """Test matching based on ST.""" scanner = ssdp.Scanner(hass) with patch('netdisco.ssdp.scan', return_value=[Mock(st="mock-st", location=None)]), patch.dict( gn_ssdp.SSDP['st'], {'mock-st': ['mock-domain']}), patch.object( hass.config_entries.flow, 'async_init', return_value=mock_coro()) as mock_init: await scanner.async_scan(None) assert len(mock_init.mock_calls) == 1 assert mock_init.mock_calls[0][1][0] == 'mock-domain' assert mock_init.mock_calls[0][2]['context'] == {'source': 'ssdp'}
async def test_scan_description_fetch_fail(hass, aioclient_mock, exc): """Test failing to fetch description.""" aioclient_mock.get("http://1.1.1.1", exc=exc) scanner = ssdp.Scanner(hass, {}) async def _mock_async_scan(*args, async_callback=None, **kwargs): await async_callback( { "st": "mock-st", "location": "http://1.1.1.1", } ) with patch( "homeassistant.components.ssdp.async_search", side_effect=_mock_async_scan, ): await scanner.async_scan(None)
async def test_unexpected_exception_while_fetching(hass, aioclient_mock, caplog): """Test unexpected exception while fetching.""" aioclient_mock.get( "http://1.1.1.1", text=""" <root> <device> <deviceType>ABC</deviceType> <serialNumber>\xff\xff\xff\xff</serialNumber> </device> </root> """, ) scanner = ssdp.Scanner( hass, { "mock-domain": [ { ssdp.ATTR_UPNP_DEVICE_TYPE: "ABC", } ] }, ) async def _mock_async_scan(*args, async_callback=None, **kwargs): await async_callback( { "st": "mock-st", "location": "http://1.1.1.1", } ) with patch( "homeassistant.components.ssdp.ElementTree.fromstring", side_effect=ValueError ), patch( "homeassistant.components.ssdp.async_search", side_effect=_mock_async_scan, ), patch.object( hass.config_entries.flow, "async_init", return_value=mock_coro() ) as mock_init: await scanner.async_scan(None) assert len(mock_init.mock_calls) == 0 assert "Failed to fetch ssdp data from: http://1.1.1.1" in caplog.text
async def test_scan_not_all_match(hass, aioclient_mock): """Test match fails if some specified attribute values differ.""" aioclient_mock.get( "http://1.1.1.1", text=""" <root> <device> <deviceType>Paulus</deviceType> <manufacturer>Paulus</manufacturer> </device> </root> """, ) scanner = ssdp.Scanner( hass, { "mock-domain": [ { ssdp.ATTR_UPNP_DEVICE_TYPE: "Paulus", ssdp.ATTR_UPNP_MANUFACTURER: "Not-Paulus", } ] }, ) async def _mock_async_scan(*args, async_callback=None, **kwargs): await async_callback( { "st": "mock-st", "location": "http://1.1.1.1", } ) with patch( "homeassistant.components.ssdp.async_search", side_effect=_mock_async_scan, ), patch.object( hass.config_entries.flow, "async_init", return_value=mock_coro() ) as mock_init: await scanner.async_scan(None) assert not mock_init.mock_calls
async def test_scan_description_parse_fail(hass, aioclient_mock): """Test invalid XML.""" aioclient_mock.get( "http://1.1.1.1", text=""" <root>INVALIDXML """, ) scanner = ssdp.Scanner(hass, {}) async def _inject_entry(*args, **kwargs): scanner.async_store_entry( Mock(st="mock-st", location="http://1.1.1.1", values={}) ) with patch( "homeassistant.components.ssdp.async_search", side_effect=_inject_entry, ): await scanner.async_scan(None)
async def test_invalid_characters(hass, aioclient_mock): """Test that we replace bad characters with placeholders.""" aioclient_mock.get( "http://1.1.1.1", text=""" <root> <device> <deviceType>ABC</deviceType> <serialNumber>\xff\xff\xff\xff</serialNumber> </device> </root> """, ) scanner = ssdp.Scanner( hass, {"mock-domain": [{ ssdp.ATTR_UPNP_DEVICE_TYPE: "ABC", }]}, ) with patch( "netdisco.ssdp.scan", return_value=[ Mock(st="mock-st", location="http://1.1.1.1", values={}) ], ), patch.object(hass.config_entries.flow, "async_init", return_value=mock_coro()) as mock_init: await scanner.async_scan(None) assert len(mock_init.mock_calls) == 1 assert mock_init.mock_calls[0][1][0] == "mock-domain" assert mock_init.mock_calls[0][2]["context"] == {"source": "ssdp"} assert mock_init.mock_calls[0][2]["data"] == { "ssdp_location": "http://1.1.1.1", "ssdp_st": "mock-st", "deviceType": "ABC", "serialNumber": "ÿÿÿÿ", }
async def test_scan_match_upnp_devicedesc(hass, aioclient_mock, key): """Test matching based on UPnP device description data.""" aioclient_mock.get( "http://1.1.1.1", text=f""" <root> <device> <{key}>Paulus</{key}> </device> </root> """, ) scanner = ssdp.Scanner(hass, {"mock-domain": [{key: "Paulus"}]}) async def _mock_async_scan(*args, async_callback=None, **kwargs): for _ in range(5): await async_callback( { "st": "mock-st", "location": "http://1.1.1.1", } ) with patch( "homeassistant.components.ssdp.async_search", side_effect=_mock_async_scan, ), patch.object( hass.config_entries.flow, "async_init", return_value=mock_coro() ) as mock_init: await scanner.async_scan(None) # If we get duplicate respones, ensure we only look it up once assert len(aioclient_mock.mock_calls) == 1 assert len(mock_init.mock_calls) == 1 assert mock_init.mock_calls[0][1][0] == "mock-domain" assert mock_init.mock_calls[0][2]["context"] == { "source": config_entries.SOURCE_SSDP }
async def test_scan_description_parse_fail(hass, aioclient_mock): """Test invalid XML.""" aioclient_mock.get( "http://1.1.1.1", text=""" <root>INVALIDXML """, ) scanner = ssdp.Scanner(hass, {}) async def _mock_async_scan(*args, async_callback=None, **kwargs): await async_callback( { "st": "mock-st", "location": "http://1.1.1.1", } ) with patch( "homeassistant.components.ssdp.async_search", side_effect=_mock_async_scan, ): await scanner.async_scan(None)
async def test_scan_match_st(hass, caplog): """Test matching based on ST.""" scanner = ssdp.Scanner(hass, {"mock-domain": [{"st": "mock-st"}]}) async def _mock_async_scan(*args, async_callback=None, **kwargs): await async_callback( { "st": "mock-st", "location": None, "usn": "mock-usn", "server": "mock-server", "ext": "", } ) with patch( "homeassistant.components.ssdp.async_search", side_effect=_mock_async_scan, ), patch.object( hass.config_entries.flow, "async_init", return_value=mock_coro() ) as mock_init: await scanner.async_scan(None) assert len(mock_init.mock_calls) == 1 assert mock_init.mock_calls[0][1][0] == "mock-domain" assert mock_init.mock_calls[0][2]["context"] == { "source": config_entries.SOURCE_SSDP } assert mock_init.mock_calls[0][2]["data"] == { ssdp.ATTR_SSDP_ST: "mock-st", ssdp.ATTR_SSDP_LOCATION: None, ssdp.ATTR_SSDP_USN: "mock-usn", ssdp.ATTR_SSDP_SERVER: "mock-server", ssdp.ATTR_SSDP_EXT: "", } assert "Failed to fetch ssdp data" not in caplog.text