def test_gateway_scan_filter_match(self): """Test match function of gateway filter.""" # pylint: disable=too-many-locals gateway_1 = GatewayDescriptor(name='KNX-Interface', ip_addr='10.1.1.11', port=3761, local_interface='en1', local_ip='110.1.1.100', supports_tunnelling=True, supports_routing=False) gateway_2 = GatewayDescriptor(name='KNX-Router', ip_addr='10.1.1.12', port=3761, local_interface='en1', local_ip='10.1.1.100', supports_tunnelling=False, supports_routing=True) filter_tunnel = GatewayScanFilter(tunnelling=True) filter_router = GatewayScanFilter(routing=True) filter_name = GatewayScanFilter(name="KNX-Router") filter_no_tunnel = GatewayScanFilter(tunnelling=False) self.assertTrue(filter_tunnel.match(gateway_1)) self.assertFalse(filter_tunnel.match(gateway_2)) self.assertFalse(filter_router.match(gateway_1)) self.assertTrue(filter_router.match(gateway_2)) self.assertFalse(filter_name.match(gateway_1)) self.assertTrue(filter_name.match(gateway_2)) self.assertFalse(filter_no_tunnel.match(gateway_1)) self.assertTrue(filter_no_tunnel.match(gateway_2))
def response_rec_callback( self, knxipframe: KNXIPFrame, source: HPAI, _: KNXIPTransport ) -> None: """Verify and handle knxipframe. Callback from internal transport.""" if not isinstance(knxipframe.body, DescriptionResponse): logger.warning("Wrong knxipframe for DescriptionRequest: %s", knxipframe) return self.response_received_event.set() # Set gateway descriptior attribute gateway = GatewayDescriptor( ip_addr=self.transport.remote_addr[0], port=self.transport.remote_addr[1], local_ip=self.transport.getsockname()[0], ) gateway.parse_dibs(knxipframe.body.dibs) self.gateway_descriptor = gateway
def _gateway_descriptor(ip: str, port: int) -> GatewayDescriptor: """Get mock gw descriptor.""" return GatewayDescriptor( "Test", ip, port, "eth0", "127.0.0.1", supports_routing=True, supports_tunnelling=True, )
def test_gateway_descriptor(self): """Test string representation of GatewayDescriptor.""" gateway_descriptor = GatewayDescriptor(name='KNX-Interface', ip_addr='192.168.2.3', port=1234, local_interface='en1', local_ip='192.168.2.50', supports_tunnelling=True, supports_routing=False) self.assertEqual( str(gateway_descriptor), '<GatewayDescriptor name="KNX-Interface" addr="192.168.2.3:1234" local="192.168.2.50@en1" routing="False" tunnelling="True" />' )
def test_gateway_descriptor(self): """Test string representation of GatewayDescriptor.""" gateway_descriptor = GatewayDescriptor( name="KNX-Interface", ip_addr="192.168.2.3", port=1234, local_interface="en1", local_ip="192.168.2.50", supports_tunnelling=True, supports_routing=False, individual_address=IndividualAddress("1.1.1"), ) assert str(gateway_descriptor) == "1.1.1 - KNX-Interface @ 192.168.2.3:1234"
def _gateway_descriptor( ip: str, port: int, supports_tunnelling_tcp: bool = False ) -> GatewayDescriptor: """Get mock gw descriptor.""" return GatewayDescriptor( name="Test", ip_addr=ip, port=port, local_interface="eth0", local_ip="127.0.0.1", supports_routing=True, supports_tunnelling=True, supports_tunnelling_tcp=supports_tunnelling_tcp, )
def test_parser(self, raw, expected): """Test parsing GatewayDescriptor objects from real-world responses.""" response = KNXIPFrame() response.from_knx(raw) assert isinstance(response.body, (SearchResponse, SearchResponseExtended)) descriptor = GatewayDescriptor( ip_addr=response.body.control_endpoint.ip_addr, port=response.body.control_endpoint.port, ) descriptor.parse_dibs(response.body.dibs) assert descriptor.supports_routing is expected["supports_routing"] assert descriptor.supports_tunnelling is expected["supports_tunnelling"] assert descriptor.supports_tunnelling_tcp is expected[ "supports_tunnelling_tcp"] assert descriptor.supports_secure is expected["supports_secure"] assert descriptor.routing_requires_secure is expected[ "routing_requires_secure"] assert (descriptor.tunnelling_requires_secure is expected["tunnelling_requires_secure"])
def test_search_response_reception(self): """Test function of gateway scanner.""" # pylint: disable=protected-access xknx = XKNX(loop=self.loop) gateway_scanner = GatewayScanner(xknx) search_response = fake_router_search_response(xknx) udp_client = unittest.mock.create_autospec(UDPClient) udp_client.local_addr = ("192.168.42.50", 0, "en1") udp_client.getsockname.return_value = ("192.168.42.50", 0) router_gw_descriptor = GatewayDescriptor(name="Gira KNX/IP-Router", ip_addr="192.168.42.10", port=3671, local_interface="en1", local_ip="192.168.42.50", supports_tunnelling=True, supports_routing=True) self.assertEqual(gateway_scanner.found_gateways, []) gateway_scanner._response_rec_callback(search_response, udp_client) self.assertEqual(str(gateway_scanner.found_gateways[0]), str(router_gw_descriptor))
def setUp(self): """Set up test class.""" self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) self.gateway_desc_interface = GatewayDescriptor( name="KNX-Interface", ip_addr="10.1.1.11", port=3761, local_interface="en1", local_ip="110.1.1.100", supports_tunnelling=True, supports_routing=False, ) self.gateway_desc_router = GatewayDescriptor( name="KNX-Router", ip_addr="10.1.1.12", port=3761, local_interface="en1", local_ip="10.1.1.100", supports_tunnelling=False, supports_routing=True, ) self.gateway_desc_both = GatewayDescriptor( name="Gira KNX/IP-Router", ip_addr="192.168.42.10", port=3671, local_interface="en1", local_ip="192.168.42.50", supports_tunnelling=True, supports_routing=True, ) self.fake_interfaces = ["lo0", "en0", "en1"] self.fake_ifaddresses = { "lo0": { 2: [{ "addr": "127.0.0.1", "netmask": "255.0.0.0", "peer": "127.0.0.1" }], 30: [ { "addr": "::1", "netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128", "peer": "::1", "flags": 0, }, { "addr": "fe80::1%lo0", "netmask": "ffff:ffff:ffff:ffff::/64", "flags": 0, }, ], }, "en0": { 18: [{ "addr": "FF:FF:00:00:00:00" }] }, "en1": { 18: [{ "addr": "FF:FF:00:00:00:01" }], 30: [{ "addr": "fe80::1234:1234:1234:1234%en1", "netmask": "ffff:ffff:ffff:ffff::/64", "flags": 1024, }], 2: [{ "addr": "10.1.1.2", "netmask": "255.255.255.0", "broadcast": "10.1.1.255", }], }, }
class TestGatewayScanner: """Test class for xknx/io/GatewayScanner objects.""" gateway_desc_interface = GatewayDescriptor( name="KNX-Interface", ip_addr="10.1.1.11", port=3761, local_interface="en1", local_ip="110.1.1.100", supports_tunnelling=True, supports_routing=False, ) gateway_desc_router = GatewayDescriptor( name="KNX-Router", ip_addr="10.1.1.12", port=3761, local_interface="en1", local_ip="10.1.1.100", supports_tunnelling=False, supports_routing=True, ) gateway_desc_both = GatewayDescriptor( name="Gira KNX/IP-Router", ip_addr="192.168.42.10", port=3671, local_interface="en1", local_ip="192.168.42.50", supports_tunnelling=True, supports_routing=True, individual_address=IndividualAddress("1.1.0"), ) gateway_desc_neither = GatewayDescriptor( name="AC/S 1.1.1 Application Control", ip_addr="10.1.1.15", port=3671, local_interface="en1", local_ip="192.168.42.50", supports_tunnelling=False, supports_routing=False, ) fake_interfaces = ["lo0", "en0", "en1"] fake_ifaddresses = { "lo0": { 2: [{ "addr": "127.0.0.1", "netmask": "255.0.0.0", "peer": "127.0.0.1" }], 30: [ { "addr": "::1", "netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128", "peer": "::1", "flags": 0, }, { "addr": "fe80::1%lo0", "netmask": "ffff:ffff:ffff:ffff::/64", "flags": 0, }, ], }, "en0": { 18: [{ "addr": "FF:FF:00:00:00:00" }] }, "en1": { 18: [{ "addr": "FF:FF:00:00:00:01" }], 30: [{ "addr": "fe80::1234:1234:1234:1234%en1", "netmask": "ffff:ffff:ffff:ffff::/64", "flags": 1024, }], 2: [{ "addr": "10.1.1.2", "netmask": "255.255.255.0", "broadcast": "10.1.1.255", }], }, } def test_gateway_scan_filter_match(self): """Test match function of gateway filter.""" filter_default = GatewayScanFilter() filter_tunnel = GatewayScanFilter(tunnelling=True) filter_router = GatewayScanFilter(routing=True) filter_name = GatewayScanFilter(name="KNX-Router") filter_no_tunnel = GatewayScanFilter(tunnelling=False) filter_no_router = GatewayScanFilter(routing=False) filter_tunnel_and_router = GatewayScanFilter(tunnelling=True, routing=True) assert filter_default.match(self.gateway_desc_interface) assert filter_default.match(self.gateway_desc_router) assert filter_default.match(self.gateway_desc_both) assert not filter_default.match(self.gateway_desc_neither) assert filter_tunnel.match(self.gateway_desc_interface) assert not filter_tunnel.match(self.gateway_desc_router) assert filter_tunnel.match(self.gateway_desc_both) assert not filter_tunnel.match(self.gateway_desc_neither) assert not filter_router.match(self.gateway_desc_interface) assert filter_router.match(self.gateway_desc_router) assert filter_router.match(self.gateway_desc_both) assert not filter_router.match(self.gateway_desc_neither) assert not filter_name.match(self.gateway_desc_interface) assert filter_name.match(self.gateway_desc_router) assert not filter_name.match(self.gateway_desc_both) assert not filter_name.match(self.gateway_desc_neither) assert not filter_no_tunnel.match(self.gateway_desc_interface) assert filter_no_tunnel.match(self.gateway_desc_router) assert not filter_no_tunnel.match(self.gateway_desc_both) assert not filter_no_tunnel.match(self.gateway_desc_neither) assert filter_no_router.match(self.gateway_desc_interface) assert not filter_no_router.match(self.gateway_desc_router) assert not filter_no_router.match(self.gateway_desc_both) assert not filter_no_router.match(self.gateway_desc_neither) assert not filter_tunnel_and_router.match(self.gateway_desc_interface) assert not filter_tunnel_and_router.match(self.gateway_desc_router) assert filter_tunnel_and_router.match(self.gateway_desc_both) assert not filter_tunnel_and_router.match(self.gateway_desc_neither) def test_search_response_reception(self): """Test function of gateway scanner.""" xknx = XKNX() gateway_scanner = GatewayScanner(xknx) test_search_response = fake_router_search_response(xknx) udp_client_mock = create_autospec(UDPClient) udp_client_mock.local_addr = ("192.168.42.50", 0) udp_client_mock.getsockname.return_value = ("192.168.42.50", 0) assert gateway_scanner.found_gateways == [] gateway_scanner._response_rec_callback( test_search_response, udp_client_mock, interface="en1", ) assert str(gateway_scanner.found_gateways[0]) == str( self.gateway_desc_both) assert len(gateway_scanner.found_gateways) == 1 gateway_scanner._response_rec_callback( test_search_response, udp_client_mock, interface="eth1", ) assert len(gateway_scanner.found_gateways) == 1 @patch("xknx.io.gateway_scanner.netifaces", autospec=True) async def test_scan_timeout(self, netifaces_mock): """Test gateway scanner timeout.""" xknx = XKNX() # No interface shall be found netifaces_mock.interfaces.return_value = [] gateway_scanner = GatewayScanner(xknx) gateway_scanner._response_received_event.wait = MagicMock( side_effect=asyncio.TimeoutError()) timed_out_scan = await gateway_scanner.scan() # Unsuccessfull scan() returns None assert timed_out_scan == [] @patch("xknx.io.gateway_scanner.netifaces", autospec=True) @patch("xknx.io.GatewayScanner._search_interface", autospec=True) async def test_send_search_requests(self, _search_interface_mock, netifaces_mock): """Test finding all valid interfaces to send search requests to. No requests are sent.""" xknx = XKNX() netifaces_mock.interfaces.return_value = self.fake_interfaces netifaces_mock.ifaddresses = lambda interface: self.fake_ifaddresses[ interface] netifaces_mock.AF_INET = 2 async def async_none(): return None _search_interface_mock.return_value = asyncio.ensure_future( async_none()) gateway_scanner = GatewayScanner(xknx, timeout_in_seconds=0) test_scan = await gateway_scanner.scan() assert _search_interface_mock.call_count == 2 expected_calls = [ ((gateway_scanner, "lo0", "127.0.0.1"), ), ((gateway_scanner, "en1", "10.1.1.2"), ), ] assert _search_interface_mock.call_args_list == expected_calls assert test_scan == []
def setUp(self): """Set up test class.""" self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) self.gateway_desc_interface = GatewayDescriptor( name='KNX-Interface', ip_addr='10.1.1.11', port=3761, local_interface='en1', local_ip='110.1.1.100', supports_tunnelling=True, supports_routing=False) self.gateway_desc_router = GatewayDescriptor(name='KNX-Router', ip_addr='10.1.1.12', port=3761, local_interface='en1', local_ip='10.1.1.100', supports_tunnelling=False, supports_routing=True) self.gateway_desc_both = GatewayDescriptor(name="Gira KNX/IP-Router", ip_addr="192.168.42.10", port=3671, local_interface="en1", local_ip="192.168.42.50", supports_tunnelling=True, supports_routing=True) self.fake_interfaces = ['lo0', 'en0', 'en1'] self.fake_ifaddresses = { 'lo0': { 2: [{ 'addr': '127.0.0.1', 'netmask': '255.0.0.0', 'peer': '127.0.0.1' }], 30: [{ 'addr': '::1', 'netmask': 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128', 'peer': '::1', 'flags': 0 }, { 'addr': 'fe80::1%lo0', 'netmask': 'ffff:ffff:ffff:ffff::/64', 'flags': 0 }] }, 'en0': { 18: [{ 'addr': 'FF:FF:00:00:00:00' }] }, 'en1': { 18: [{ 'addr': 'FF:FF:00:00:00:01' }], 30: [{ 'addr': 'fe80::1234:1234:1234:1234%en1', 'netmask': 'ffff:ffff:ffff:ffff::/64', 'flags': 1024 }], 2: [{ 'addr': '10.1.1.2', 'netmask': '255.255.255.0', 'broadcast': '10.1.1.255' }] } }
def _gateway_descriptor(ip: str, port: int) -> GatewayDescriptor: """Get mock gw descriptor.""" return GatewayDescriptor("Test", ip, port, "eth0", "127.0.0.1", True)
class TestGatewayScanner: """Test class for xknx/io/GatewayScanner objects.""" gateway_desc_interface = GatewayDescriptor( name="KNX-Interface", ip_addr="10.1.1.11", port=3761, local_interface="en1", local_ip="10.1.1.100", supports_tunnelling=True, supports_routing=False, ) gateway_desc_router = GatewayDescriptor( name="KNX-Router", ip_addr="10.1.1.12", port=3761, local_interface="en1", local_ip="10.1.1.100", supports_tunnelling=False, supports_routing=True, ) gateway_desc_both = GatewayDescriptor( name="Gira KNX/IP-Router", ip_addr="192.168.42.10", port=3671, local_interface="en1", local_ip="192.168.42.50", supports_tunnelling=True, supports_tunnelling_tcp=True, supports_routing=True, individual_address=IndividualAddress("1.1.0"), ) gateway_desc_both.tunnelling_requires_secure = False gateway_desc_neither = GatewayDescriptor( name="AC/S 1.1.1 Application Control", ip_addr="10.1.1.15", port=3671, local_interface="en1", local_ip="192.168.42.50", supports_tunnelling=False, supports_routing=False, ) gateway_desc_secure_tunnel = GatewayDescriptor( name="KNX-Interface", ip_addr="10.1.1.11", port=3761, local_interface="en1", local_ip="10.1.1.111", supports_routing=False, supports_tunnelling=True, supports_tunnelling_tcp=True, supports_secure=True, ) gateway_desc_secure_tunnel.tunnelling_requires_secure = True def test_gateway_scan_filter_match(self): """Test match function of gateway filter.""" filter_default = GatewayScanFilter() filter_tunnel = GatewayScanFilter(tunnelling=True) filter_tcp_tunnel = GatewayScanFilter(tunnelling_tcp=True, secure=None) filter_secure_tunnel = GatewayScanFilter(tunnelling_tcp=True, secure=True) filter_router = GatewayScanFilter(routing=True) filter_name = GatewayScanFilter(name="KNX-Router") filter_no_tunnel = GatewayScanFilter(tunnelling=False) filter_no_router = GatewayScanFilter(routing=False) filter_tunnel_and_router = GatewayScanFilter(tunnelling=True, routing=True) assert filter_default.match(self.gateway_desc_interface) assert filter_default.match(self.gateway_desc_router) assert filter_default.match(self.gateway_desc_both) assert not filter_default.match(self.gateway_desc_neither) assert not filter_default.match(self.gateway_desc_secure_tunnel) assert filter_tunnel.match(self.gateway_desc_interface) assert not filter_tunnel.match(self.gateway_desc_router) assert filter_tunnel.match(self.gateway_desc_both) assert not filter_tunnel.match(self.gateway_desc_neither) assert not filter_tunnel.match(self.gateway_desc_secure_tunnel) assert not filter_tcp_tunnel.match(self.gateway_desc_interface) assert not filter_tcp_tunnel.match(self.gateway_desc_router) assert filter_tcp_tunnel.match(self.gateway_desc_both) assert not filter_tcp_tunnel.match(self.gateway_desc_neither) assert filter_tcp_tunnel.match(self.gateway_desc_secure_tunnel) assert not filter_secure_tunnel.match(self.gateway_desc_interface) assert not filter_secure_tunnel.match(self.gateway_desc_router) assert not filter_secure_tunnel.match(self.gateway_desc_both) assert not filter_secure_tunnel.match(self.gateway_desc_neither) assert filter_secure_tunnel.match(self.gateway_desc_secure_tunnel) assert not filter_router.match(self.gateway_desc_interface) assert filter_router.match(self.gateway_desc_router) assert filter_router.match(self.gateway_desc_both) assert not filter_router.match(self.gateway_desc_neither) assert not filter_router.match(self.gateway_desc_secure_tunnel) assert not filter_name.match(self.gateway_desc_interface) assert filter_name.match(self.gateway_desc_router) assert not filter_name.match(self.gateway_desc_both) assert not filter_name.match(self.gateway_desc_neither) assert not filter_name.match(self.gateway_desc_secure_tunnel) assert not filter_no_tunnel.match(self.gateway_desc_interface) assert filter_no_tunnel.match(self.gateway_desc_router) assert not filter_no_tunnel.match(self.gateway_desc_both) assert not filter_no_tunnel.match(self.gateway_desc_neither) assert not filter_no_tunnel.match(self.gateway_desc_secure_tunnel) assert filter_no_router.match(self.gateway_desc_interface) assert not filter_no_router.match(self.gateway_desc_router) assert not filter_no_router.match(self.gateway_desc_both) assert not filter_no_router.match(self.gateway_desc_neither) assert not filter_no_router.match(self.gateway_desc_secure_tunnel) assert not filter_tunnel_and_router.match(self.gateway_desc_interface) assert not filter_tunnel_and_router.match(self.gateway_desc_router) assert filter_tunnel_and_router.match(self.gateway_desc_both) assert not filter_tunnel_and_router.match(self.gateway_desc_neither) assert not filter_tunnel_and_router.match( self.gateway_desc_secure_tunnel) def test_search_response_reception(self): """Test function of gateway scanner.""" xknx = XKNX() gateway_scanner = GatewayScanner(xknx) test_search_response = fake_router_search_response() udp_transport_mock = create_autospec(UDPTransport) udp_transport_mock.local_addr = ("192.168.42.50", 0) udp_transport_mock.getsockname.return_value = ("192.168.42.50", 0) assert not gateway_scanner.found_gateways gateway_scanner._response_rec_callback( test_search_response, HPAI("192.168.42.50", 0), udp_transport_mock, interface="en1", ) assert len(gateway_scanner.found_gateways) == 1 gateway_scanner._response_rec_callback( test_search_response, HPAI("192.168.42.230", 0), udp_transport_mock, interface="eth1", ) assert len(gateway_scanner.found_gateways) == 1 assert str(gateway_scanner.found_gateways[ test_search_response.body.control_endpoint]) == str( self.gateway_desc_both) @patch("xknx.io.gateway_scanner.UDPTransport.connect") @patch("xknx.io.gateway_scanner.UDPTransport.send") @patch( "xknx.io.gateway_scanner.UDPTransport.getsockname", return_value=("10.1.1.2", 56789), ) async def test_scan_timeout( self, getsockname_mock, udp_transport_send_mock, udp_transport_connect_mock, time_travel, ): """Test gateway scanner timeout.""" xknx = XKNX() gateway_scanner = GatewayScanner(xknx) timed_out_scan_task = asyncio.create_task(gateway_scanner.scan()) await time_travel(gateway_scanner.timeout_in_seconds) # Unsuccessfull scan() returns empty list assert await timed_out_scan_task == [] @patch("xknx.io.gateway_scanner.UDPTransport.connect") @patch("xknx.io.gateway_scanner.UDPTransport.send") async def test_async_scan_timeout( self, udp_transport_send_mock, udp_transport_connect_mock, time_travel, ): """Test gateway scanner timeout for async generator.""" async def test(): xknx = XKNX() async for _ in GatewayScanner(xknx).async_scan(): break else: return True # timeout with patch( "xknx.io.util.get_default_local_ip", return_value="10.1.1.2", ), patch( "xknx.io.gateway_scanner.UDPTransport.getsockname", return_value=("10.1.1.2", 56789), ): timed_out_scan_task = asyncio.create_task(test()) await time_travel(3) assert await timed_out_scan_task # no matching interface found with patch( "xknx.io.util.get_default_local_ip", return_value=None, ): timed_out_scan_task = asyncio.create_task(test()) await time_travel(3) with pytest.raises(XKNXException): await timed_out_scan_task @patch("xknx.io.gateway_scanner.UDPTransport.connect") @patch("xknx.io.gateway_scanner.UDPTransport.send") async def test_async_scan_exit( self, udp_transport_send_mock, udp_transport_connect_mock, time_travel, ): """Test gateway scanner timeout for async generator.""" xknx = XKNX() test_search_response = fake_router_search_response() udp_transport_mock = Mock() udp_transport_mock.local_addr = ("10.1.1.2", 56789) gateway_scanner = GatewayScanner(xknx, local_ip="10.1.1.2") async def test(): async for gateway in gateway_scanner.async_scan(): assert isinstance(gateway, GatewayDescriptor) return True return False with patch( "xknx.io.gateway_scanner.UDPTransport.getsockname", return_value=("10.1.1.2", 56789), ), patch("xknx.io.gateway_scanner.UDPTransport.register_callback" ) as register_callback_mock: scan_task = asyncio.create_task(test()) await time_travel(0) _fished_response_rec_callback = register_callback_mock.call_args.args[ 0] _fished_response_rec_callback( test_search_response, HPAI("192.168.42.50", 0), udp_transport_mock, ) assert await scan_task await time_travel(0) # for task cleanup @patch("xknx.io.gateway_scanner.UDPTransport.connect") @patch("xknx.io.gateway_scanner.UDPTransport.send") async def test_send_search_requests( self, udp_transport_send_mock, udp_transport_connect_mock, ): """Test if both search requests are sent per interface.""" xknx = XKNX() gateway_scanner = GatewayScanner(xknx, timeout_in_seconds=0) with patch( "xknx.io.util.get_default_local_ip", return_value="10.1.1.2", ), patch( "xknx.io.util.get_local_interface_name", return_value="en_0123", ), patch( "xknx.io.gateway_scanner.UDPTransport.getsockname", return_value=("10.1.1.2", 56789), ): await gateway_scanner.scan() assert udp_transport_connect_mock.call_count == 1 assert udp_transport_send_mock.call_count == 2 frame_1 = udp_transport_send_mock.call_args_list[0].args[0] frame_2 = udp_transport_send_mock.call_args_list[1].args[0] assert isinstance(frame_1.body, SearchRequestExtended) assert isinstance(frame_2.body, SearchRequest) assert frame_1.body.discovery_endpoint == HPAI(ip_addr="10.1.1.2", port=56789)