Forking what is left of ZeroNet and hopefully adding an AT Proto Frontend/Proxy
at main 274 lines 11 kB view raw
1import socket 2from urllib.parse import urlparse 3 4import pytest 5import mock 6 7from util import UpnpPunch as upnp 8 9 10@pytest.fixture 11def mock_socket(): 12 mock_socket = mock.MagicMock() 13 mock_socket.recv = mock.MagicMock(return_value=b'Hello') 14 mock_socket.bind = mock.MagicMock() 15 mock_socket.send_to = mock.MagicMock() 16 17 return mock_socket 18 19 20@pytest.fixture 21def url_obj(): 22 return urlparse('http://192.168.1.1/ctrlPoint.xml') 23 24 25@pytest.fixture(params=['WANPPPConnection', 'WANIPConnection']) 26def igd_profile(request): 27 return """<root><serviceList><service> 28 <serviceType>urn:schemas-upnp-org:service:{}:1</serviceType> 29 <serviceId>urn:upnp-org:serviceId:wanpppc:pppoa</serviceId> 30 <controlURL>/upnp/control/wanpppcpppoa</controlURL> 31 <eventSubURL>/upnp/event/wanpppcpppoa</eventSubURL> 32 <SCPDURL>/WANPPPConnection.xml</SCPDURL> 33</service></serviceList></root>""".format(request.param) 34 35 36@pytest.fixture 37def httplib_response(): 38 class FakeResponse(object): 39 def __init__(self, status=200, body='OK'): 40 self.status = status 41 self.body = body 42 43 def read(self): 44 return self.body 45 return FakeResponse 46 47 48class TestUpnpPunch(object): 49 def test_perform_m_search(self, mock_socket): 50 local_ip = '127.0.0.1' 51 52 with mock.patch('util.UpnpPunch.socket.socket', 53 return_value=mock_socket): 54 result = upnp.perform_m_search(local_ip) 55 assert result == 'Hello' 56 assert local_ip == mock_socket.bind.call_args_list[0][0][0][0] 57 assert ('239.255.255.250', 58 1900) == mock_socket.sendto.call_args_list[0][0][1] 59 60 def test_perform_m_search_socket_error(self, mock_socket): 61 mock_socket.recv.side_effect = socket.error('Timeout error') 62 63 with mock.patch('util.UpnpPunch.socket.socket', 64 return_value=mock_socket): 65 with pytest.raises(upnp.UpnpError): 66 upnp.perform_m_search('127.0.0.1') 67 68 def test_retrieve_location_from_ssdp(self, url_obj): 69 ctrl_location = url_obj.geturl() 70 parsed_location = urlparse(ctrl_location) 71 rsp = ('auth: gibberish\r\nlocation: {0}\r\n' 72 'Content-Type: text/html\r\n\r\n').format(ctrl_location) 73 result = upnp._retrieve_location_from_ssdp(rsp) 74 assert result == parsed_location 75 76 def test_retrieve_location_from_ssdp_no_header(self): 77 rsp = 'auth: gibberish\r\nContent-Type: application/json\r\n\r\n' 78 with pytest.raises(upnp.IGDError): 79 upnp._retrieve_location_from_ssdp(rsp) 80 81 def test_retrieve_igd_profile(self, url_obj): 82 with mock.patch('urllib.request.urlopen') as mock_urlopen: 83 upnp._retrieve_igd_profile(url_obj) 84 mock_urlopen.assert_called_with(url_obj.geturl(), timeout=5) 85 86 def test_retrieve_igd_profile_timeout(self, url_obj): 87 with mock.patch('urllib.request.urlopen') as mock_urlopen: 88 mock_urlopen.side_effect = socket.error('Timeout error') 89 with pytest.raises(upnp.IGDError): 90 upnp._retrieve_igd_profile(url_obj) 91 92 def test_parse_igd_profile_service_type(self, igd_profile): 93 control_path, upnp_schema = upnp._parse_igd_profile(igd_profile) 94 assert control_path == '/upnp/control/wanpppcpppoa' 95 assert upnp_schema in ('WANPPPConnection', 'WANIPConnection',) 96 97 def test_parse_igd_profile_no_ctrlurl(self, igd_profile): 98 igd_profile = igd_profile.replace('controlURL', 'nope') 99 with pytest.raises(upnp.IGDError): 100 control_path, upnp_schema = upnp._parse_igd_profile(igd_profile) 101 102 def test_parse_igd_profile_no_schema(self, igd_profile): 103 igd_profile = igd_profile.replace('Connection', 'nope') 104 with pytest.raises(upnp.IGDError): 105 control_path, upnp_schema = upnp._parse_igd_profile(igd_profile) 106 107 def test_create_open_message_parsable(self): 108 from xml.parsers.expat import ExpatError 109 msg, _ = upnp._create_open_message('127.0.0.1', 8888) 110 try: 111 upnp.parseString(msg) 112 except ExpatError as e: 113 pytest.fail('Incorrect XML message: {}'.format(e)) 114 115 def test_create_open_message_contains_right_stuff(self): 116 settings = {'description': 'test desc', 117 'protocol': 'test proto', 118 'upnp_schema': 'test schema'} 119 msg, fn_name = upnp._create_open_message('127.0.0.1', 8888, **settings) 120 assert fn_name == 'AddPortMapping' 121 assert '127.0.0.1' in msg 122 assert '8888' in msg 123 assert settings['description'] in msg 124 assert settings['protocol'] in msg 125 assert settings['upnp_schema'] in msg 126 127 def test_parse_for_errors_bad_rsp(self, httplib_response): 128 rsp = httplib_response(status=500) 129 with pytest.raises(upnp.IGDError) as err: 130 upnp._parse_for_errors(rsp) 131 assert 'Unable to parse' in str(err.value) 132 133 def test_parse_for_errors_error(self, httplib_response): 134 soap_error = ('<document>' 135 '<errorCode>500</errorCode>' 136 '<errorDescription>Bad request</errorDescription>' 137 '</document>') 138 rsp = httplib_response(status=500, body=soap_error) 139 with pytest.raises(upnp.IGDError) as err: 140 upnp._parse_for_errors(rsp) 141 assert 'SOAP request error' in str(err.value) 142 143 def test_parse_for_errors_good_rsp(self, httplib_response): 144 rsp = httplib_response(status=200) 145 assert rsp == upnp._parse_for_errors(rsp) 146 147 def test_send_requests_success(self): 148 with mock.patch( 149 'util.UpnpPunch._send_soap_request') as mock_send_request: 150 mock_send_request.return_value = mock.MagicMock(status=200) 151 upnp._send_requests(['msg'], None, None, None) 152 153 assert mock_send_request.called 154 155 def test_send_requests_failed(self): 156 with mock.patch( 157 'util.UpnpPunch._send_soap_request') as mock_send_request: 158 mock_send_request.return_value = mock.MagicMock(status=500) 159 with pytest.raises(upnp.UpnpError): 160 upnp._send_requests(['msg'], None, None, None) 161 162 assert mock_send_request.called 163 164 def test_collect_idg_data(self): 165 pass 166 167 @mock.patch('util.UpnpPunch._get_local_ips') 168 @mock.patch('util.UpnpPunch._collect_idg_data') 169 @mock.patch('util.UpnpPunch._send_requests') 170 def test_ask_to_open_port_success(self, mock_send_requests, 171 mock_collect_idg, mock_local_ips): 172 mock_collect_idg.return_value = {'upnp_schema': 'schema-yo'} 173 mock_local_ips.return_value = ['192.168.0.12'] 174 175 result = upnp.ask_to_open_port(retries=5) 176 177 soap_msg = mock_send_requests.call_args[0][0][0][0] 178 179 assert result is True 180 181 assert mock_collect_idg.called 182 assert '192.168.0.12' in soap_msg 183 assert '15441' in soap_msg 184 assert 'schema-yo' in soap_msg 185 186 @mock.patch('util.UpnpPunch._get_local_ips') 187 @mock.patch('util.UpnpPunch._collect_idg_data') 188 @mock.patch('util.UpnpPunch._send_requests') 189 def test_ask_to_open_port_failure(self, mock_send_requests, 190 mock_collect_idg, mock_local_ips): 191 mock_local_ips.return_value = ['192.168.0.12'] 192 mock_collect_idg.return_value = {'upnp_schema': 'schema-yo'} 193 mock_send_requests.side_effect = upnp.UpnpError() 194 195 with pytest.raises(upnp.UpnpError): 196 upnp.ask_to_open_port() 197 198 @mock.patch('util.UpnpPunch._collect_idg_data') 199 @mock.patch('util.UpnpPunch._send_requests') 200 def test_orchestrate_soap_request(self, mock_send_requests, 201 mock_collect_idg): 202 soap_mock = mock.MagicMock() 203 args = ['127.0.0.1', 31337, soap_mock, 'upnp-test', {'upnp_schema': 204 'schema-yo'}] 205 mock_collect_idg.return_value = args[-1] 206 207 upnp._orchestrate_soap_request(*args[:-1]) 208 209 assert mock_collect_idg.called 210 soap_mock.assert_called_with( 211 *args[:2] + ['upnp-test', 'UDP', 'schema-yo']) 212 assert mock_send_requests.called 213 214 @mock.patch('util.UpnpPunch._collect_idg_data') 215 @mock.patch('util.UpnpPunch._send_requests') 216 def test_orchestrate_soap_request_without_desc(self, mock_send_requests, 217 mock_collect_idg): 218 soap_mock = mock.MagicMock() 219 args = ['127.0.0.1', 31337, soap_mock, {'upnp_schema': 'schema-yo'}] 220 mock_collect_idg.return_value = args[-1] 221 222 upnp._orchestrate_soap_request(*args[:-1]) 223 224 assert mock_collect_idg.called 225 soap_mock.assert_called_with(*args[:2] + [None, 'UDP', 'schema-yo']) 226 assert mock_send_requests.called 227 228 def test_create_close_message_parsable(self): 229 from xml.parsers.expat import ExpatError 230 msg, _ = upnp._create_close_message('127.0.0.1', 8888) 231 try: 232 upnp.parseString(msg) 233 except ExpatError as e: 234 pytest.fail('Incorrect XML message: {}'.format(e)) 235 236 def test_create_close_message_contains_right_stuff(self): 237 settings = {'protocol': 'test proto', 238 'upnp_schema': 'test schema'} 239 msg, fn_name = upnp._create_close_message('127.0.0.1', 8888, ** 240 settings) 241 assert fn_name == 'DeletePortMapping' 242 assert '8888' in msg 243 assert settings['protocol'] in msg 244 assert settings['upnp_schema'] in msg 245 246 @mock.patch('util.UpnpPunch._get_local_ips') 247 @mock.patch('util.UpnpPunch._orchestrate_soap_request') 248 def test_communicate_with_igd_success(self, mock_orchestrate, 249 mock_get_local_ips): 250 mock_get_local_ips.return_value = ['192.168.0.12'] 251 upnp._communicate_with_igd() 252 assert mock_get_local_ips.called 253 assert mock_orchestrate.called 254 255 @mock.patch('util.UpnpPunch._get_local_ips') 256 @mock.patch('util.UpnpPunch._orchestrate_soap_request') 257 def test_communicate_with_igd_succeed_despite_single_failure( 258 self, mock_orchestrate, mock_get_local_ips): 259 mock_get_local_ips.return_value = ['192.168.0.12'] 260 mock_orchestrate.side_effect = [upnp.UpnpError, None] 261 upnp._communicate_with_igd(retries=2) 262 assert mock_get_local_ips.called 263 assert mock_orchestrate.called 264 265 @mock.patch('util.UpnpPunch._get_local_ips') 266 @mock.patch('util.UpnpPunch._orchestrate_soap_request') 267 def test_communicate_with_igd_total_failure(self, mock_orchestrate, 268 mock_get_local_ips): 269 mock_get_local_ips.return_value = ['192.168.0.12'] 270 mock_orchestrate.side_effect = [upnp.UpnpError, upnp.IGDError] 271 with pytest.raises(upnp.UpnpError): 272 upnp._communicate_with_igd(retries=2) 273 assert mock_get_local_ips.called 274 assert mock_orchestrate.called