test_provider_dnscom.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. # coding=utf-8
  2. """
  3. Unit tests for DnscomProvider
  4. @author: GitHub Copilot
  5. """
  6. from base_test import BaseProviderTestCase, unittest, patch
  7. from ddns.provider.dnscom import DnscomProvider
  8. class TestDnscomProvider(BaseProviderTestCase):
  9. """Test cases for DnscomProvider"""
  10. def setUp(self):
  11. """Set up test fixtures"""
  12. super(TestDnscomProvider, self).setUp()
  13. self.auth_id = "test_api_key"
  14. self.auth_token = "test_api_secret"
  15. def test_class_constants(self):
  16. """Test DnscomProvider class constants"""
  17. self.assertEqual(DnscomProvider.API, "https://www.51dns.com")
  18. self.assertEqual(DnscomProvider.content_type, "application/x-www-form-urlencoded")
  19. self.assertTrue(DnscomProvider.decode_response)
  20. def test_init_with_basic_config(self):
  21. """Test DnscomProvider initialization with basic configuration"""
  22. provider = DnscomProvider(self.auth_id, self.auth_token)
  23. self.assertEqual(provider.auth_id, self.auth_id)
  24. self.assertEqual(provider.auth_token, self.auth_token)
  25. self.assertEqual(provider.API, "https://www.51dns.com")
  26. @patch("ddns.provider.dnscom.time")
  27. @patch("ddns.provider.dnscom.md5")
  28. def test_signature_generation(self, mock_md5, mock_time):
  29. """Test _signature method generates correct signature"""
  30. # Mock time and hash
  31. mock_time.return_value = 1640995200 # Fixed timestamp
  32. mock_hash = mock_md5.return_value
  33. mock_hash.hexdigest.return_value = "test_hash_value"
  34. provider = DnscomProvider(self.auth_id, self.auth_token)
  35. params = {"action": "test", "domain": "example.com"}
  36. signed_params = provider._signature(params)
  37. # Verify standard parameters are added
  38. self.assertEqual(signed_params["apiKey"], self.auth_id)
  39. self.assertEqual(signed_params["timestamp"], 1640995200)
  40. self.assertEqual(signed_params["hash"], "test_hash_value")
  41. self.assertIn("action", signed_params)
  42. self.assertIn("domain", signed_params)
  43. def test_signature_filters_none_params(self):
  44. """Test _signature method filters out None parameters"""
  45. provider = DnscomProvider(self.auth_id, self.auth_token)
  46. with patch("ddns.provider.dnscom.time") as mock_time, patch("ddns.provider.dnscom.md5") as mock_md5:
  47. # Mock time to return a fixed timestamp
  48. mock_time.return_value = 1640995200
  49. mock_hash = mock_md5.return_value
  50. mock_hash.hexdigest.return_value = "test_hash"
  51. params = {"action": "test", "domain": "example.com", "ttl": None, "line": None}
  52. signed_params = provider._signature(params)
  53. # Verify None parameters were filtered out
  54. self.assertNotIn("ttl", signed_params)
  55. self.assertNotIn("line", signed_params)
  56. self.assertIn("action", signed_params)
  57. self.assertIn("domain", signed_params)
  58. def test_request_success(self):
  59. """Test _request method with successful response"""
  60. provider = DnscomProvider(self.auth_id, self.auth_token)
  61. with patch.object(provider, "_signature") as mock_signature, patch.object(provider, "_http") as mock_http:
  62. mock_signature.return_value = {"apiKey": self.auth_id, "hash": "test_hash"}
  63. mock_http.return_value = {"code": 0, "data": {"result": "success"}}
  64. result = provider._request("test", domain="example.com")
  65. mock_signature.assert_called_once_with({"domain": "example.com"})
  66. mock_http.assert_called_once_with(
  67. "POST", "/api/test/", body={"apiKey": self.auth_id, "hash": "test_hash"}
  68. )
  69. self.assertEqual(result, {"result": "success"})
  70. def test_request_failure_none_response(self):
  71. """Test _request method with None response"""
  72. provider = DnscomProvider(self.auth_id, self.auth_token)
  73. with patch.object(provider, "_signature") as mock_signature, patch.object(provider, "_http") as mock_http:
  74. mock_signature.return_value = {"apiKey": self.auth_id}
  75. mock_http.return_value = None
  76. with self.assertRaises(Exception) as cm:
  77. provider._request("test", domain="example.com")
  78. self.assertIn("response data is none", str(cm.exception))
  79. def test_request_failure_api_error(self):
  80. """Test _request method with API error response"""
  81. provider = DnscomProvider(self.auth_id, self.auth_token)
  82. with patch.object(provider, "_signature") as mock_signature, patch.object(provider, "_http") as mock_http:
  83. mock_signature.return_value = {"apiKey": self.auth_id}
  84. mock_http.return_value = {"code": 1, "message": "Invalid API key"}
  85. with self.assertRaises(Exception) as cm:
  86. provider._request("test", domain="example.com")
  87. self.assertIn("api error: Invalid API key", str(cm.exception))
  88. def test_query_zone_id_success(self):
  89. """Test _query_zone_id method with successful response"""
  90. provider = DnscomProvider(self.auth_id, self.auth_token)
  91. with patch.object(provider, "_request") as mock_request:
  92. mock_request.return_value = {"domainID": "example.com"}
  93. result = provider._query_zone_id("example.com")
  94. mock_request.assert_called_once_with("domain/getsingle", domainID="example.com")
  95. self.assertEqual(result, "example.com")
  96. def test_query_zone_id_not_found(self):
  97. """Test _query_zone_id method when domain is not found"""
  98. provider = DnscomProvider(self.auth_id, self.auth_token)
  99. with patch.object(provider, "_request") as mock_request:
  100. mock_request.return_value = None
  101. result = provider._query_zone_id("notfound.com")
  102. mock_request.assert_called_once_with("domain/getsingle", domainID="notfound.com")
  103. self.assertIsNone(result)
  104. def test_query_record_success(self):
  105. """Test _query_record method with successful response"""
  106. provider = DnscomProvider(self.auth_id, self.auth_token)
  107. with patch.object(provider, "_request") as mock_request:
  108. mock_request.return_value = {
  109. "data": [
  110. {"record": "www", "type": "A", "recordID": "123", "value": "1.2.3.4"},
  111. {"record": "mail", "type": "A", "recordID": "456", "value": "5.6.7.8"},
  112. ]
  113. }
  114. result = provider._query_record("example.com", "www", "example.com", "A", None, {})
  115. mock_request.assert_called_once_with("record/list", domainID="example.com", host="www", pageSize=500)
  116. self.assertIsNotNone(result)
  117. if result: # Type narrowing
  118. self.assertEqual(result["recordID"], "123")
  119. self.assertEqual(result["record"], "www")
  120. def test_query_record_with_line(self):
  121. """Test _query_record method with line parameter"""
  122. provider = DnscomProvider(self.auth_id, self.auth_token)
  123. with patch.object(provider, "_request") as mock_request:
  124. mock_request.return_value = {
  125. "data": [
  126. {"record": "www", "type": "A", "recordID": "123", "viewID": "1"},
  127. {"record": "www", "type": "A", "recordID": "456", "viewID": "2"},
  128. ]
  129. }
  130. result = provider._query_record("example.com", "www", "example.com", "A", "2", {})
  131. self.assertIsNotNone(result)
  132. if result: # Type narrowing
  133. self.assertEqual(result["recordID"], "456")
  134. self.assertEqual(result["viewID"], "2")
  135. def test_query_record_not_found(self):
  136. """Test _query_record method when no matching record is found"""
  137. provider = DnscomProvider(self.auth_id, self.auth_token)
  138. with patch.object(provider, "_request") as mock_request:
  139. mock_request.return_value = {
  140. "data": [{"record": "mail", "type": "A", "recordID": "456", "value": "5.6.7.8"}]
  141. }
  142. result = provider._query_record("example.com", "www", "example.com", "A", None, {})
  143. self.assertIsNone(result)
  144. def test_query_record_empty_response(self):
  145. """Test _query_record method with empty response"""
  146. provider = DnscomProvider(self.auth_id, self.auth_token)
  147. with patch.object(provider, "_request") as mock_request:
  148. mock_request.return_value = None
  149. result = provider._query_record("example.com", "www", "example.com", "A", None, {})
  150. self.assertIsNone(result)
  151. def test_create_record_success(self):
  152. """Test _create_record method with successful creation"""
  153. provider = DnscomProvider(self.auth_id, self.auth_token)
  154. with patch.object(provider, "_request") as mock_request:
  155. mock_request.return_value = {"recordID": "123456"}
  156. result = provider._create_record("example.com", "www", "example.com", "1.2.3.4", "A", 300, "1", {})
  157. mock_request.assert_called_once_with(
  158. "record/create",
  159. domainID="example.com",
  160. value="1.2.3.4",
  161. host="www",
  162. type="A",
  163. TTL=300,
  164. viewID="1",
  165. remark=provider.remark,
  166. )
  167. self.assertTrue(result)
  168. def test_create_record_with_extra_params(self):
  169. """Test _create_record method with extra parameters"""
  170. provider = DnscomProvider(self.auth_id, self.auth_token)
  171. with patch.object(provider, "_request") as mock_request:
  172. mock_request.return_value = {"recordID": "123456"}
  173. extra = {"remark": "Custom remark", "priority": 10}
  174. result = provider._create_record("example.com", "www", "example.com", "1.2.3.4", "A", 300, "1", extra)
  175. mock_request.assert_called_once_with(
  176. "record/create",
  177. domainID="example.com",
  178. value="1.2.3.4",
  179. host="www",
  180. type="A",
  181. TTL=300,
  182. viewID="1",
  183. remark="Custom remark",
  184. priority=10,
  185. )
  186. self.assertTrue(result)
  187. def test_create_record_failure(self):
  188. """Test _create_record method with failed creation"""
  189. provider = DnscomProvider(self.auth_id, self.auth_token)
  190. with patch.object(provider, "_request") as mock_request:
  191. mock_request.return_value = {"error": "Domain not found"}
  192. result = provider._create_record("example.com", "www", "example.com", "1.2.3.4", "A", None, None, {})
  193. self.assertFalse(result)
  194. def test_update_record_success(self):
  195. """Test _update_record method with successful update"""
  196. provider = DnscomProvider(self.auth_id, self.auth_token)
  197. old_record = {"recordID": "123456", "remark": "Old remark"}
  198. with patch.object(provider, "_request") as mock_request:
  199. mock_request.return_value = {"success": True}
  200. result = provider._update_record("example.com", old_record, "5.6.7.8", "A", 600, None, {})
  201. mock_request.assert_called_once_with(
  202. "record/modify", domainID="example.com", recordID="123456", newvalue="5.6.7.8", newTTL=600
  203. )
  204. self.assertTrue(result)
  205. def test_update_record_with_extra_params(self):
  206. """Test _update_record method with extra parameters"""
  207. provider = DnscomProvider(self.auth_id, self.auth_token)
  208. old_record = {"recordID": "123456", "remark": "Old remark"}
  209. with patch.object(provider, "_request") as mock_request:
  210. mock_request.return_value = {"success": True}
  211. extra = {"remark": "New remark"}
  212. result = provider._update_record("example.com", old_record, "5.6.7.8", "A", 600, "1", extra)
  213. mock_request.assert_called_once_with(
  214. "record/modify", domainID="example.com", recordID="123456", newvalue="5.6.7.8", newTTL=600
  215. )
  216. self.assertTrue(result)
  217. def test_update_record_failure(self):
  218. """Test _update_record method with failed update"""
  219. provider = DnscomProvider(self.auth_id, self.auth_token)
  220. old_record = {"recordID": "123456"}
  221. with patch.object(provider, "_request") as mock_request:
  222. mock_request.return_value = None
  223. result = provider._update_record("example.com", old_record, "5.6.7.8", "A", None, None, {})
  224. self.assertFalse(result)
  225. class TestDnscomProviderIntegration(BaseProviderTestCase):
  226. """Integration test cases for DnscomProvider - testing with minimal mocking"""
  227. def setUp(self):
  228. """Set up test fixtures"""
  229. super(TestDnscomProviderIntegration, self).setUp()
  230. self.auth_id = "test_api_key"
  231. self.auth_token = "test_api_secret"
  232. def test_full_workflow_create_new_record(self):
  233. """Test complete workflow for creating a new record"""
  234. provider = DnscomProvider(self.auth_id, self.auth_token)
  235. # Mock only the HTTP layer to simulate API responses
  236. with patch.object(provider, "_request") as mock_request:
  237. # Simulate API responses in order: zone query, record query, record creation
  238. mock_request.side_effect = [
  239. {"domainID": "example.com"}, # _query_zone_id response
  240. {"data": []}, # _query_record response (no existing record)
  241. {"recordID": "123456"}, # _create_record response
  242. ]
  243. result = provider.set_record("www.example.com", "1.2.3.4", "A", 300, "1")
  244. self.assertTrue(result)
  245. # Verify the actual API calls made
  246. self.assertEqual(mock_request.call_count, 3)
  247. mock_request.assert_any_call("domain/getsingle", domainID="example.com")
  248. mock_request.assert_any_call("record/list", domainID="example.com", host="www", pageSize=500)
  249. mock_request.assert_any_call(
  250. "record/create",
  251. domainID="example.com",
  252. value="1.2.3.4",
  253. host="www",
  254. type="A",
  255. TTL=300,
  256. viewID="1",
  257. remark="Managed by [DDNS v0.0.0](https://ddns.newfuture.cc)",
  258. )
  259. def test_full_workflow_update_existing_record(self):
  260. """Test complete workflow for updating an existing record"""
  261. provider = DnscomProvider(self.auth_id, self.auth_token)
  262. # Mock only the HTTP layer to simulate raw API responses
  263. with patch.object(provider, "_http") as mock_http:
  264. # Simulate raw HTTP API responses as they would come from the server
  265. mock_http.side_effect = [
  266. {"code": 0, "data": {"domainID": "example.com"}}, # domain/getsingle response
  267. {
  268. "code": 0,
  269. "data": { # record/list response
  270. "data": [{"record": "www", "type": "A", "recordID": "123456", "value": "5.6.7.8"}]
  271. },
  272. },
  273. {"code": 0, "data": {"recordID": "123456", "success": True}}, # record/modify response
  274. ]
  275. result = provider.set_record("www.example.com", "1.2.3.4", "A", 300, "1")
  276. self.assertTrue(result)
  277. # Verify the actual HTTP calls were made (3 calls total)
  278. self.assertEqual(mock_http.call_count, 3)
  279. def test_full_workflow_zone_not_found(self):
  280. """Test complete workflow when zone is not found"""
  281. provider = DnscomProvider(self.auth_id, self.auth_token)
  282. with patch.object(provider, "_request") as mock_request:
  283. # Simulate API returning None for zone query
  284. mock_request.return_value = None
  285. result = provider.set_record("www.nonexistent.com", "1.2.3.4", "A")
  286. self.assertFalse(result)
  287. def test_full_workflow_create_failure(self):
  288. """Test complete workflow when record creation fails"""
  289. provider = DnscomProvider(self.auth_id, self.auth_token)
  290. with patch.object(provider, "_request") as mock_request:
  291. # Simulate responses: zone found, no existing record, creation fails
  292. mock_request.side_effect = [
  293. {"domainID": "example.com"}, # _query_zone_id response
  294. {"data": []}, # _query_record response (no existing record)
  295. {"error": "Domain not found"}, # _create_record fails
  296. ]
  297. result = provider.set_record("www.example.com", "1.2.3.4", "A")
  298. self.assertFalse(result)
  299. def test_full_workflow_update_failure(self):
  300. """Test complete workflow when record update fails"""
  301. provider = DnscomProvider(self.auth_id, self.auth_token)
  302. with patch.object(provider, "_request") as mock_request:
  303. # Simulate responses: zone found, existing record found, update fails
  304. mock_request.side_effect = [
  305. {"domainID": "example.com"}, # _query_zone_id response
  306. { # _query_record response (existing record found)
  307. "data": [{"record": "www", "type": "A", "recordID": "123456", "value": "5.6.7.8"}]
  308. },
  309. None, # _update_record fails
  310. ]
  311. result = provider.set_record("www.example.com", "1.2.3.4", "A")
  312. self.assertFalse(result)
  313. if __name__ == "__main__":
  314. unittest.main()