test_provider_he.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. # coding=utf-8
  2. """
  3. Unit tests for HeProvider (Hurricane Electric)
  4. @author: GitHub Copilot
  5. """
  6. from base_test import BaseProviderTestCase, unittest, patch, MagicMock
  7. from ddns.provider.he import HeProvider
  8. class TestHeProvider(BaseProviderTestCase):
  9. """Test cases for HeProvider"""
  10. def setUp(self):
  11. """Set up test fixtures"""
  12. super(TestHeProvider, self).setUp()
  13. # Override default auth values for HE provider - HE uses empty id
  14. self.id = ""
  15. self.token = "test_password"
  16. def test_init_with_basic_config(self):
  17. """Test HeProvider initialization with basic configuration"""
  18. # HE provider should use empty id and only token
  19. provider = HeProvider("", self.token)
  20. self.assertEqual(provider.id, "")
  21. self.assertEqual(provider.token, self.token)
  22. self.assertEqual(provider.endpoint, "https://dyn.dns.he.net")
  23. self.assertFalse(provider.decode_response)
  24. def test_class_constants(self):
  25. """Test HeProvider class constants"""
  26. provider = HeProvider("", self.token)
  27. self.assertEqual(provider.endpoint, "https://dyn.dns.he.net")
  28. self.assertFalse(provider.decode_response)
  29. # ContentType should be form-encoded
  30. from ddns.provider._base import TYPE_FORM
  31. self.assertEqual(provider.content_type, TYPE_FORM)
  32. def test_validate_success_with_token_only(self):
  33. """Test _validate method passes with token only (correct usage)"""
  34. provider = HeProvider("", self.token)
  35. # Should not raise any exception
  36. provider._validate()
  37. def test_validate_fails_with_id(self):
  38. """Test _validate method fails when id is provided"""
  39. with self.assertRaises(ValueError) as cm:
  40. HeProvider("some_id", self.token)
  41. self.assertIn("does not use `id`", str(cm.exception))
  42. def test_validate_fails_without_token(self):
  43. """Test _validate method fails when token is missing"""
  44. with self.assertRaises(ValueError) as cm:
  45. HeProvider("", "")
  46. self.assertIn("requires `token(password)`", str(cm.exception))
  47. @patch.object(HeProvider, "_http")
  48. def test_set_record_success_good_response(self, mock_http):
  49. """Test set_record method with 'good' response"""
  50. mock_http.return_value = "good 192.168.1.1"
  51. provider = HeProvider("", self.token)
  52. result = provider.set_record("example.com", "192.168.1.1", "A")
  53. # Verify the result
  54. self.assertTrue(result)
  55. # Verify _http was called with correct parameters
  56. mock_http.assert_called_once()
  57. args, kwargs = mock_http.call_args
  58. self.assertEqual(args[0], "POST") # method
  59. self.assertEqual(args[1], "/nic/update") # path
  60. # Check body parameters
  61. body = kwargs["body"]
  62. self.assertEqual(body["hostname"], "example.com")
  63. self.assertEqual(body["myip"], "192.168.1.1")
  64. self.assertEqual(body["password"], self.token)
  65. @patch.object(HeProvider, "_http")
  66. def test_set_record_success_nochg_response(self, mock_http):
  67. """Test set_record method with 'nochg' response"""
  68. mock_http.return_value = "nochg 192.168.1.1"
  69. provider = HeProvider("", self.token)
  70. result = provider.set_record("test.example.com", "192.168.1.1", "A")
  71. # Verify the result
  72. self.assertTrue(result)
  73. # Verify _http was called with correct parameters
  74. mock_http.assert_called_once()
  75. args, kwargs = mock_http.call_args
  76. self.assertEqual(args[0], "POST")
  77. self.assertEqual(args[1], "/nic/update")
  78. # Check body parameters
  79. body = kwargs["body"]
  80. self.assertEqual(body["hostname"], "test.example.com")
  81. self.assertEqual(body["myip"], "192.168.1.1")
  82. self.assertEqual(body["password"], self.token)
  83. @patch.object(HeProvider, "_http")
  84. def test_set_record_ipv6_address(self, mock_http):
  85. """Test set_record method with IPv6 address"""
  86. mock_http.return_value = "good 2001:db8::1"
  87. provider = HeProvider("", self.token)
  88. result = provider.set_record("ipv6.example.com", "2001:db8::1", "AAAA")
  89. # Verify the result
  90. self.assertTrue(result)
  91. # Check body parameters
  92. args, kwargs = mock_http.call_args
  93. body = kwargs["body"]
  94. self.assertEqual(body["hostname"], "ipv6.example.com")
  95. self.assertEqual(body["myip"], "2001:db8::1")
  96. @patch.object(HeProvider, "_http")
  97. def test_set_record_with_all_parameters(self, mock_http):
  98. """Test set_record method with all optional parameters"""
  99. mock_http.return_value = "good 10.0.0.1"
  100. provider = HeProvider("", self.token)
  101. result = provider.set_record(
  102. domain="full.example.com", value="10.0.0.1", record_type="A", ttl=300, line="default", extra_param="test"
  103. )
  104. # Verify the result
  105. self.assertTrue(result)
  106. # Check that core parameters are still correct
  107. args, kwargs = mock_http.call_args
  108. body = kwargs["body"]
  109. self.assertEqual(body["hostname"], "full.example.com")
  110. self.assertEqual(body["myip"], "10.0.0.1")
  111. self.assertEqual(body["password"], self.token)
  112. @patch.object(HeProvider, "_http")
  113. def test_set_record_empty_response_error(self, mock_http):
  114. """Test set_record method with empty response (should return False)"""
  115. mock_http.return_value = "" # Empty response
  116. provider = HeProvider("", self.token)
  117. provider.logger = MagicMock()
  118. result = provider.set_record("example.com", "192.168.1.1")
  119. self.assertFalse(result)
  120. # Verify error was logged
  121. provider.logger.error.assert_called_once_with("HE API error: %s", "")
  122. @patch.object(HeProvider, "_http")
  123. def test_set_record_none_response_error(self, mock_http):
  124. """Test set_record method with None response (should return False)"""
  125. mock_http.return_value = None # None response
  126. provider = HeProvider("", self.token)
  127. provider.logger = MagicMock()
  128. result = provider.set_record("example.com", "192.168.1.1")
  129. self.assertFalse(result)
  130. # Verify error was logged - None causes TypeError when slicing
  131. provider.logger.error.assert_called_once()
  132. args = provider.logger.error.call_args[0]
  133. self.assertEqual(args[0], "Error updating record for %s: %s")
  134. self.assertEqual(args[1], "example.com")
  135. self.assertIsInstance(args[2], TypeError)
  136. @patch.object(HeProvider, "_http")
  137. def test_set_record_http_exception(self, mock_http):
  138. """Test set_record method when _http raises an exception"""
  139. mock_http.side_effect = Exception("Network error")
  140. provider = HeProvider("", self.token)
  141. provider.logger = MagicMock()
  142. result = provider.set_record("example.com", "192.168.1.1")
  143. self.assertFalse(result)
  144. # Verify error was logged
  145. provider.logger.error.assert_called_once()
  146. args = provider.logger.error.call_args[0]
  147. self.assertEqual(args[0], "Error updating record for %s: %s")
  148. self.assertEqual(args[1], "example.com")
  149. self.assertIsInstance(args[2], Exception)
  150. @patch.object(HeProvider, "_http")
  151. def test_set_record_error_response(self, mock_http):
  152. """Test set_record method with error response"""
  153. mock_http.return_value = "badauth"
  154. provider = HeProvider("", self.token)
  155. provider.logger = MagicMock()
  156. result = provider.set_record("example.com", "192.168.1.1")
  157. self.assertFalse(result)
  158. # Verify error was logged
  159. provider.logger.error.assert_called_once_with("HE API error: %s", "badauth")
  160. @patch.object(HeProvider, "_http")
  161. def test_set_record_abuse_response(self, mock_http):
  162. """Test set_record method with abuse response"""
  163. mock_http.return_value = "abuse"
  164. provider = HeProvider("", self.token)
  165. provider.logger = MagicMock()
  166. result = provider.set_record("example.com", "192.168.1.1")
  167. self.assertFalse(result)
  168. # Verify error was logged
  169. provider.logger.error.assert_called_once_with("HE API error: %s", "abuse")
  170. @patch.object(HeProvider, "_http")
  171. def test_set_record_notfqdn_response(self, mock_http):
  172. """Test set_record method with notfqdn response"""
  173. mock_http.return_value = "notfqdn"
  174. provider = HeProvider("", self.token)
  175. provider.logger = MagicMock()
  176. result = provider.set_record("example.com", "192.168.1.1")
  177. self.assertFalse(result)
  178. # Verify error was logged
  179. provider.logger.error.assert_called_once_with("HE API error: %s", "notfqdn")
  180. @patch.object(HeProvider, "_http")
  181. def test_set_record_partial_good_response(self, mock_http):
  182. """Test set_record method with partial 'good' response"""
  183. mock_http.return_value = "good" # Just 'good' without IP
  184. provider = HeProvider("", self.token)
  185. provider.logger = MagicMock()
  186. result = provider.set_record("example.com", "192.168.1.1")
  187. # Should return True as success
  188. self.assertTrue(result)
  189. # Verify success was logged
  190. provider.logger.info.assert_any_call("HE API response: %s", "good")
  191. @patch.object(HeProvider, "_http")
  192. def test_set_record_partial_nochg_response(self, mock_http):
  193. """Test set_record method with partial 'nochg' response"""
  194. mock_http.return_value = "nochg" # Just 'nochg' without IP
  195. provider = HeProvider("", self.token)
  196. provider.logger = MagicMock()
  197. result = provider.set_record("example.com", "192.168.1.1")
  198. # Should return True as success
  199. self.assertTrue(result)
  200. # Verify success was logged
  201. provider.logger.info.assert_any_call("HE API response: %s", "nochg")
  202. def test_set_record_logger_info_called(self):
  203. """Test that logger.info is called with correct parameters"""
  204. provider = HeProvider("", self.token)
  205. # Mock the logger and _http
  206. provider.logger = MagicMock()
  207. with patch.object(provider, "_http") as mock_http:
  208. mock_http.return_value = "good 192.168.1.1"
  209. provider.set_record("example.com", "192.168.1.1", "A")
  210. # Verify logger.info was called with correct parameters for the initial log
  211. provider.logger.info.assert_any_call("%s => %s(%s)", "example.com", "192.168.1.1", "A")
  212. def test_set_record_logger_info_on_success(self):
  213. """Test that logger.info is called on success"""
  214. provider = HeProvider("", self.token)
  215. # Mock the logger and _http
  216. provider.logger = MagicMock()
  217. with patch.object(provider, "_http") as mock_http:
  218. mock_http.return_value = "good 192.168.1.1"
  219. provider.set_record("example.com", "192.168.1.1", "A")
  220. # Verify logger.info was called for successful response
  221. calls = provider.logger.info.call_args_list
  222. self.assertEqual(len(calls), 2) # Initial log and success log
  223. def test_set_record_logger_error_called(self):
  224. """Test that logger.error is called on error response"""
  225. provider = HeProvider("", self.token)
  226. # Mock the logger and _http
  227. provider.logger = MagicMock()
  228. with patch.object(provider, "_http") as mock_http:
  229. mock_http.return_value = "badauth"
  230. result = provider.set_record("example.com", "192.168.1.1", "A")
  231. self.assertFalse(result)
  232. # Verify logger.error was called with correct parameters
  233. provider.logger.error.assert_called_once_with("HE API error: %s", "badauth")
  234. class TestHeProviderIntegration(BaseProviderTestCase):
  235. """Integration tests for HeProvider"""
  236. def test_full_workflow_ipv4_success(self):
  237. """Test complete workflow for IPv4 record with success response"""
  238. provider = HeProvider("", "test_token")
  239. with patch.object(provider, "_http") as mock_http:
  240. mock_http.return_value = "good 1.2.3.4"
  241. result = provider.set_record("test.com", "1.2.3.4", "A", 300, "default")
  242. self.assertTrue(result)
  243. mock_http.assert_called_once()
  244. args, kwargs = mock_http.call_args
  245. self.assertEqual(args[0], "POST")
  246. self.assertEqual(args[1], "/nic/update")
  247. body = kwargs["body"]
  248. self.assertEqual(body["hostname"], "test.com")
  249. self.assertEqual(body["myip"], "1.2.3.4")
  250. self.assertEqual(body["password"], "test_token")
  251. def test_full_workflow_ipv6_success(self):
  252. """Test complete workflow for IPv6 record with success response"""
  253. provider = HeProvider("", "test_token")
  254. with patch.object(provider, "_http") as mock_http:
  255. mock_http.return_value = "good ::1"
  256. result = provider.set_record("test.com", "::1", "AAAA", 600, "telecom")
  257. self.assertTrue(result)
  258. mock_http.assert_called_once()
  259. args, kwargs = mock_http.call_args
  260. body = kwargs["body"]
  261. self.assertEqual(body["hostname"], "test.com")
  262. self.assertEqual(body["myip"], "::1")
  263. def test_full_workflow_error_handling(self):
  264. """Test complete workflow with error handling"""
  265. provider = HeProvider("", "test_token")
  266. with patch.object(provider, "_http") as mock_http:
  267. mock_http.return_value = "badauth"
  268. result = provider.set_record("test.com", "1.2.3.4", "A")
  269. self.assertFalse(result)
  270. if __name__ == "__main__":
  271. unittest.main()