TestUpnpPunch.py 11 KB

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