test_provider_namesilo.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. # coding=utf-8
  2. # type: ignore[index]
  3. """
  4. Unit tests for NameSilo DNS provider
  5. @author: NewFuture & Copilot
  6. """
  7. from base_test import BaseProviderTestCase, unittest, patch
  8. from ddns.provider.namesilo import NamesiloProvider
  9. class TestNamesiloProvider(BaseProviderTestCase):
  10. """Test cases for NameSilo DNS provider"""
  11. def setUp(self):
  12. """Set up test fixtures"""
  13. super(TestNamesiloProvider, self).setUp()
  14. self.provider = NamesiloProvider(self.id, self.token)
  15. def test_init_with_basic_config(self):
  16. """Test basic provider initialization"""
  17. self.assertProviderInitialized(self.provider)
  18. self.assertEqual(self.provider.endpoint, "https://www.namesilo.com")
  19. self.assertEqual(self.provider.content_type, "application/json")
  20. def test_init_with_custom_endpoint(self):
  21. """Test provider initialization with custom endpoint"""
  22. custom_endpoint = "https://api.custom.namesilo.com"
  23. provider = NamesiloProvider(self.id, self.token, endpoint=custom_endpoint)
  24. self.assertEqual(provider.endpoint, custom_endpoint)
  25. def test_validate_success(self):
  26. """Test successful credential validation"""
  27. # Should not raise exception
  28. try:
  29. self.provider._validate()
  30. except Exception as e:
  31. self.fail("_validate() raised unexpected exception: {}".format(e))
  32. def test_validate_missing_token(self):
  33. """Test validation with missing token"""
  34. with self.assertRaises(ValueError) as context:
  35. NamesiloProvider(self.id, "")
  36. self.assertIn("API key", str(context.exception))
  37. def test_validate_missing_id_allowed(self):
  38. """Test validation with missing ID (should be allowed)"""
  39. # Should not raise exception - ID is not strictly required for NameSilo
  40. try:
  41. provider = NamesiloProvider("", self.token)
  42. self.assertEqual(provider.token, self.token)
  43. except Exception as e:
  44. self.fail("_validate() raised unexpected exception with missing ID: {}".format(e))
  45. @patch.object(NamesiloProvider, "_http")
  46. def test_request_success(self, mock_http):
  47. """Test successful API request"""
  48. mock_response = {"reply": {"code": "300", "detail": "success", "data": {"test": "value"}}}
  49. mock_http.return_value = mock_response
  50. result = self.provider._request("testOperation", domain="example.com")
  51. # Verify request parameters
  52. mock_http.assert_called_once()
  53. args, kwargs = mock_http.call_args
  54. self.assertEqual(args[0], "GET")
  55. self.assertEqual(args[1], "/api/testOperation")
  56. expected_queries = {"domain": "example.com", "version": "1", "type": "json", "key": self.token}
  57. self.assertEqual(kwargs["queries"], expected_queries)
  58. # Verify response
  59. self.assertEqual(result["code"], "300")
  60. @patch.object(NamesiloProvider, "_http")
  61. def test_request_api_error(self, mock_http):
  62. """Test API request with error response"""
  63. mock_response = {"reply": {"code": "400", "detail": "Invalid domain"}}
  64. mock_http.return_value = mock_response
  65. # Mock logger to capture warning
  66. mock_logger = self.mock_logger(self.provider)
  67. result = self.provider._request("testOperation", domain="invalid.com")
  68. # Verify warning was logged
  69. mock_logger.warning.assert_called_once()
  70. warning_call = mock_logger.warning.call_args[0]
  71. self.assertIn("400", warning_call[1])
  72. self.assertIn("Invalid domain", warning_call[2])
  73. # Should return None on error
  74. self.assertIsNone(result)
  75. @patch.object(NamesiloProvider, "_request")
  76. def test_query_zone_id_success(self, mock_request):
  77. """Test successful zone ID query"""
  78. mock_request.return_value = {"code": "300", "domain": {"domain": "example.com"}}
  79. result = self.provider._query_zone_id("example.com")
  80. mock_request.assert_called_once_with("getDomainInfo", domain="example.com")
  81. self.assertEqual(result, "example.com")
  82. @patch.object(NamesiloProvider, "_request")
  83. def test_query_zone_id_not_found(self, mock_request):
  84. """Test zone ID query for non-existent domain"""
  85. mock_request.return_value = {"code": "400", "detail": "Domain not found"}
  86. result = self.provider._query_zone_id("nonexistent.com")
  87. self.assertIsNone(result)
  88. @patch.object(NamesiloProvider, "_request")
  89. def test_query_record_success_multiple_records(self, mock_request):
  90. """Test successful record query with multiple records"""
  91. mock_request.return_value = {
  92. "code": "300",
  93. "resource_record": [
  94. {"record_id": "12345", "host": "test", "type": "A", "value": "1.2.3.4", "ttl": "3600"},
  95. {"record_id": "67890", "host": "other", "type": "A", "value": "5.6.7.8", "ttl": "3600"},
  96. ],
  97. }
  98. result = self.provider._query_record("example.com", "test", "example.com", "A", None, {})
  99. self.assertIsNotNone(result)
  100. self.assertEqual(result["record_id"], "12345")
  101. self.assertEqual(result["host"], "test")
  102. @patch.object(NamesiloProvider, "_request")
  103. def test_query_record_not_found(self, mock_request):
  104. """Test record query when no matching record is found"""
  105. mock_request.return_value = {
  106. "code": "300",
  107. "resource_record": [
  108. {"record_id": "67890", "host": "other", "type": "A", "value": "5.6.7.8", "ttl": "3600"}
  109. ],
  110. }
  111. result = self.provider._query_record("example.com", "test", "example.com", "A", None, {})
  112. self.assertIsNone(result)
  113. @patch.object(NamesiloProvider, "_request")
  114. def test_create_record_success(self, mock_request):
  115. """Test successful record creation"""
  116. mock_request.return_value = {"code": "300", "record_id": "12345"}
  117. result = self.provider._create_record("example.com", "test", "example.com", "1.2.3.4", "A", 3600, None, {})
  118. expected_params = {
  119. "domain": "example.com",
  120. "rrtype": "A",
  121. "rrhost": "test",
  122. "rrvalue": "1.2.3.4",
  123. "rrttl": 3600,
  124. }
  125. mock_request.assert_called_once_with("dnsAddRecord", **expected_params)
  126. self.assertTrue(result)
  127. @patch.object(NamesiloProvider, "_request")
  128. def test_create_record_without_ttl(self, mock_request):
  129. """Test record creation without TTL"""
  130. mock_request.return_value = {"code": "300", "record_id": "12345"}
  131. result = self.provider._create_record("example.com", "test", "example.com", "1.2.3.4", "A", None, None, {})
  132. expected_params = {
  133. "domain": "example.com",
  134. "rrtype": "A",
  135. "rrhost": "test",
  136. "rrvalue": "1.2.3.4",
  137. "rrttl": None,
  138. }
  139. mock_request.assert_called_once_with("dnsAddRecord", **expected_params)
  140. self.assertTrue(result)
  141. @patch.object(NamesiloProvider, "_request")
  142. def test_create_record_failure(self, mock_request):
  143. """Test failed record creation"""
  144. mock_request.return_value = None
  145. result = self.provider._create_record("example.com", "test", "example.com", "invalid", "A", 3600, None, {})
  146. self.assertFalse(result)
  147. @patch.object(NamesiloProvider, "_request")
  148. def test_update_record_success(self, mock_request):
  149. """Test successful record update"""
  150. mock_request.return_value = {"code": "300"}
  151. old_record = {"record_id": "12345", "host": "test", "type": "A", "value": "1.2.3.4", "ttl": "3600"}
  152. result = self.provider._update_record("example.com", old_record, "5.6.7.8", "A", 7200, None, {})
  153. expected_params = {
  154. "domain": "example.com",
  155. "rrid": "12345",
  156. "rrhost": "test",
  157. "rrvalue": "5.6.7.8",
  158. "rrtype": "A",
  159. "rrttl": 7200,
  160. }
  161. mock_request.assert_called_once_with("dnsUpdateRecord", **expected_params)
  162. self.assertTrue(result)
  163. @patch.object(NamesiloProvider, "_request")
  164. def test_update_record_keep_existing_ttl(self, mock_request):
  165. """Test record update keeping existing TTL"""
  166. mock_request.return_value = {"code": "300"}
  167. old_record = {"record_id": "12345", "host": "test", "type": "A", "value": "1.2.3.4", "ttl": "3600"}
  168. result = self.provider._update_record("example.com", old_record, "5.6.7.8", "A", None, None, {})
  169. expected_params = {
  170. "domain": "example.com",
  171. "rrid": "12345",
  172. "rrhost": "test",
  173. "rrvalue": "5.6.7.8",
  174. "rrtype": "A",
  175. "rrttl": "3600", # Should keep existing TTL
  176. }
  177. mock_request.assert_called_once_with("dnsUpdateRecord", **expected_params)
  178. self.assertTrue(result)
  179. @patch.object(NamesiloProvider, "_request")
  180. def test_update_record_missing_record_id(self, mock_request):
  181. """Test record update with missing record_id"""
  182. old_record = {"host": "test", "type": "A", "value": "1.2.3.4"}
  183. result = self.provider._update_record("example.com", old_record, "5.6.7.8", "A", 7200, None, {})
  184. # Should not make API call and return False
  185. mock_request.assert_not_called()
  186. self.assertFalse(result)
  187. @patch.object(NamesiloProvider, "_request")
  188. def test_update_record_failure(self, mock_request):
  189. """Test failed record update"""
  190. mock_request.return_value = None
  191. old_record = {"record_id": "12345", "host": "test", "type": "A", "value": "1.2.3.4", "ttl": "3600"}
  192. result = self.provider._update_record("example.com", old_record, "5.6.7.8", "A", 7200, None, {})
  193. self.assertFalse(result)
  194. @patch.object(NamesiloProvider, "_request")
  195. def test_update_record_extra_priority_over_old_record(self, mock_request):
  196. """Test that extra parameters take priority over old_record values"""
  197. mock_request.return_value = {"reply": {"code": "300"}}
  198. old_record = {"record_id": "12345", "host": "test", "type": "A", "value": "1.2.3.4", "ttl": "3600"}
  199. # NameSilo doesn't use many extra params in update, but we verify it doesn't break
  200. extra = {"custom_field": "custom_value"}
  201. result = self.provider._update_record("example.com", old_record, "5.6.7.8", "A", 7200, None, extra)
  202. # Verify the basic call was made (NameSilo doesn't support extra params in update)
  203. mock_request.assert_called_once_with(
  204. "dnsUpdateRecord",
  205. rrid="12345",
  206. domain="example.com",
  207. rrhost="test",
  208. rrvalue="5.6.7.8",
  209. rrtype="A",
  210. rrttl=7200,
  211. )
  212. self.assertTrue(result)
  213. @patch.object(NamesiloProvider, "_http")
  214. def test_integration_set_record_update_flow(self, mock_http):
  215. """Integration test for complete set_record flow with update"""
  216. # Mock the sequence of API calls for an update scenario
  217. # 1. Domain info check (zone_id query)
  218. # 2. Record listing
  219. # 3. Record update
  220. mock_responses = [
  221. # getDomainInfo response
  222. {"reply": {"code": "300", "domain": {"domain": "example.com"}}},
  223. # dnsListRecords response
  224. {
  225. "reply": {
  226. "code": "300",
  227. "resource_record": [
  228. {"record_id": "12345", "host": "test", "type": "A", "value": "1.2.3.4", "ttl": "3600"}
  229. ],
  230. }
  231. },
  232. # dnsUpdateRecord response
  233. {"reply": {"code": "300"}},
  234. ]
  235. mock_http.side_effect = mock_responses
  236. # Execute set_record
  237. result = self.provider.set_record("test.example.com", "5.6.7.8", "A", 7200)
  238. # Verify result
  239. self.assertTrue(result)
  240. # Verify all expected API calls were made
  241. self.assertEqual(mock_http.call_count, 3)
  242. @patch.object(NamesiloProvider, "_http")
  243. def test_integration_set_record_create_flow(self, mock_http):
  244. """Integration test for complete set_record flow with create"""
  245. # Mock the sequence of API calls for a create scenario
  246. mock_responses = [
  247. # getDomainInfo response
  248. {"reply": {"code": "300", "domain": {"domain": "example.com"}}},
  249. # dnsListRecords response (no matching record)
  250. {"reply": {"code": "300", "resource_record": []}},
  251. # dnsAddRecord response
  252. {"reply": {"code": "300", "record_id": "67890"}},
  253. ]
  254. mock_http.side_effect = mock_responses
  255. # Execute set_record
  256. result = self.provider.set_record("newtest.example.com", "9.8.7.6", "A", 3600)
  257. # Verify result
  258. self.assertTrue(result)
  259. # Verify all expected API calls were made
  260. self.assertEqual(mock_http.call_count, 3)
  261. if __name__ == "__main__":
  262. unittest.main()