test_provider_dnscom.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  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.id = "test_api_key"
  14. self.token = "test_api_secret"
  15. def test_class_constants(self):
  16. """Test DnscomProvider class constants"""
  17. self.assertEqual(DnscomProvider.endpoint, "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.id, self.token)
  23. self.assertEqual(provider.id, self.id)
  24. self.assertEqual(provider.token, self.token)
  25. self.assertEqual(provider.endpoint, "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.id, self.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.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.id, self.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.id, self.token)
  61. with patch.object(provider, "_signature") as mock_signature, patch.object(provider, "_http") as mock_http:
  62. mock_signature.return_value = {"apiKey": self.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("POST", "/api/test/", body={"apiKey": self.id, "hash": "test_hash"})
  67. self.assertEqual(result, {"result": "success"})
  68. def test_request_failure_none_response(self):
  69. """Test _request method with None response"""
  70. provider = DnscomProvider(self.id, self.token)
  71. with patch.object(provider, "_signature") as mock_signature, patch.object(provider, "_http") as mock_http:
  72. mock_signature.return_value = {"apiKey": self.id}
  73. mock_http.return_value = None
  74. with self.assertRaises(Exception) as cm:
  75. provider._request("test", domain="example.com")
  76. self.assertIn("response data is none", str(cm.exception))
  77. def test_request_failure_api_error(self):
  78. """Test _request method with API error response"""
  79. provider = DnscomProvider(self.id, self.token)
  80. with patch.object(provider, "_signature") as mock_signature, patch.object(provider, "_http") as mock_http:
  81. mock_signature.return_value = {"apiKey": self.id}
  82. mock_http.return_value = {"code": 1, "message": "Invalid API key"}
  83. with self.assertRaises(Exception) as cm:
  84. provider._request("test", domain="example.com")
  85. self.assertIn("api error: Invalid API key", str(cm.exception))
  86. def test_query_zone_id_success(self):
  87. """Test _query_zone_id method with successful response"""
  88. provider = DnscomProvider(self.id, self.token)
  89. with patch.object(provider, "_request") as mock_request:
  90. mock_request.return_value = {"domainID": "example.com"}
  91. result = provider._query_zone_id("example.com")
  92. mock_request.assert_called_once_with("domain/getsingle", domainID="example.com")
  93. self.assertEqual(result, "example.com")
  94. def test_query_zone_id_not_found(self):
  95. """Test _query_zone_id method when domain is not found"""
  96. provider = DnscomProvider(self.id, self.token)
  97. with patch.object(provider, "_request") as mock_request:
  98. mock_request.return_value = None
  99. result = provider._query_zone_id("notfound.com")
  100. mock_request.assert_called_once_with("domain/getsingle", domainID="notfound.com")
  101. self.assertIsNone(result)
  102. def test_query_record_success(self):
  103. """Test _query_record method with successful response"""
  104. provider = DnscomProvider(self.id, self.token)
  105. with patch.object(provider, "_request") as mock_request:
  106. mock_request.return_value = {
  107. "data": [
  108. {"record": "www", "type": "A", "recordID": "123", "value": "1.2.3.4"},
  109. {"record": "mail", "type": "A", "recordID": "456", "value": "5.6.7.8"},
  110. ]
  111. }
  112. result = provider._query_record("example.com", "www", "example.com", "A", None, {})
  113. mock_request.assert_called_once_with("record/list", domainID="example.com", host="www", pageSize=500)
  114. self.assertIsNotNone(result)
  115. if result: # Type narrowing
  116. self.assertEqual(result["recordID"], "123")
  117. self.assertEqual(result["record"], "www")
  118. def test_query_record_with_line(self):
  119. """Test _query_record method with line parameter"""
  120. provider = DnscomProvider(self.id, self.token)
  121. with patch.object(provider, "_request") as mock_request:
  122. mock_request.return_value = {
  123. "data": [
  124. {"record": "www", "type": "A", "recordID": "123", "viewID": "1"},
  125. {"record": "www", "type": "A", "recordID": "456", "viewID": "2"},
  126. ]
  127. }
  128. result = provider._query_record("example.com", "www", "example.com", "A", "2", {})
  129. self.assertIsNotNone(result)
  130. if result: # Type narrowing
  131. self.assertEqual(result["recordID"], "456")
  132. self.assertEqual(result["viewID"], "2")
  133. def test_query_record_not_found(self):
  134. """Test _query_record method when no matching record is found"""
  135. provider = DnscomProvider(self.id, self.token)
  136. with patch.object(provider, "_request") as mock_request:
  137. mock_request.return_value = {
  138. "data": [{"record": "mail", "type": "A", "recordID": "456", "value": "5.6.7.8"}]
  139. }
  140. result = provider._query_record("example.com", "www", "example.com", "A", None, {})
  141. self.assertIsNone(result)
  142. def test_query_record_empty_response(self):
  143. """Test _query_record method with empty response"""
  144. provider = DnscomProvider(self.id, self.token)
  145. with patch.object(provider, "_request") as mock_request:
  146. mock_request.return_value = None
  147. result = provider._query_record("example.com", "www", "example.com", "A", None, {})
  148. self.assertIsNone(result)
  149. def test_create_record_success(self):
  150. """Test _create_record method with successful creation"""
  151. provider = DnscomProvider(self.id, self.token)
  152. with patch.object(provider, "_request") as mock_request:
  153. mock_request.return_value = {"recordID": "123456"}
  154. result = provider._create_record("example.com", "www", "example.com", "1.2.3.4", "A", 300, "1", {})
  155. mock_request.assert_called_once_with(
  156. "record/create",
  157. domainID="example.com",
  158. value="1.2.3.4",
  159. host="www",
  160. type="A",
  161. TTL=300,
  162. viewID="1",
  163. remark=provider.remark,
  164. )
  165. self.assertTrue(result)
  166. def test_create_record_with_extra_params(self):
  167. """Test _create_record method with extra parameters"""
  168. provider = DnscomProvider(self.id, self.token)
  169. with patch.object(provider, "_request") as mock_request:
  170. mock_request.return_value = {"recordID": "123456"}
  171. extra = {"remark": "Custom remark", "priority": 10}
  172. result = provider._create_record("example.com", "www", "example.com", "1.2.3.4", "A", 300, "1", extra)
  173. mock_request.assert_called_once_with(
  174. "record/create",
  175. domainID="example.com",
  176. value="1.2.3.4",
  177. host="www",
  178. type="A",
  179. TTL=300,
  180. viewID="1",
  181. remark="Custom remark",
  182. priority=10,
  183. )
  184. self.assertTrue(result)
  185. def test_create_record_failure(self):
  186. """Test _create_record method with failed creation"""
  187. provider = DnscomProvider(self.id, self.token)
  188. with patch.object(provider, "_request") as mock_request:
  189. mock_request.return_value = {"error": "Domain not found"}
  190. result = provider._create_record("example.com", "www", "example.com", "1.2.3.4", "A", None, None, {})
  191. self.assertFalse(result)
  192. def test_update_record_success(self):
  193. """Test _update_record method with successful update"""
  194. provider = DnscomProvider(self.id, self.token)
  195. old_record = {"recordID": "123456", "remark": "Old remark"}
  196. with patch.object(provider, "_request") as mock_request:
  197. mock_request.return_value = {"success": True}
  198. result = provider._update_record("example.com", old_record, "5.6.7.8", "A", 600, None, {})
  199. mock_request.assert_called_once_with(
  200. "record/modify", domainID="example.com", recordID="123456", newvalue="5.6.7.8", newTTL=600
  201. )
  202. self.assertTrue(result)
  203. def test_update_record_with_extra_params(self):
  204. """Test _update_record method with extra parameters"""
  205. provider = DnscomProvider(self.id, self.token)
  206. old_record = {"recordID": "123456", "remark": "Old remark"}
  207. with patch.object(provider, "_request") as mock_request:
  208. mock_request.return_value = {"success": True}
  209. extra = {"remark": "New remark"}
  210. result = provider._update_record("example.com", old_record, "5.6.7.8", "A", 600, "1", extra)
  211. mock_request.assert_called_once_with(
  212. "record/modify", domainID="example.com", recordID="123456", newvalue="5.6.7.8", newTTL=600
  213. )
  214. self.assertTrue(result)
  215. def test_update_record_extra_priority_over_old_record(self):
  216. """Test that extra parameters take priority over old_record values"""
  217. provider = DnscomProvider(self.id, self.token)
  218. old_record = {"recordID": "123456", "remark": "Old remark"}
  219. with patch.object(provider, "_request") as mock_request:
  220. mock_request.return_value = {"success": True}
  221. # extra should override old_record's remark
  222. extra = {"remark": "New remark from extra"}
  223. result = provider._update_record("example.com", old_record, "5.6.7.8", "A", 600, "1", extra)
  224. # Verify that extra["remark"] was set correctly with priority
  225. self.assertEqual(extra["remark"], "New remark from extra")
  226. self.assertTrue(result)
  227. def test_update_record_failure(self):
  228. """Test _update_record method with failed update"""
  229. provider = DnscomProvider(self.id, self.token)
  230. old_record = {"recordID": "123456"}
  231. with patch.object(provider, "_request") as mock_request:
  232. mock_request.return_value = None
  233. result = provider._update_record("example.com", old_record, "5.6.7.8", "A", None, None, {})
  234. self.assertFalse(result)
  235. class TestDnscomProviderIntegration(BaseProviderTestCase):
  236. """Integration test cases for DnscomProvider - testing with minimal mocking"""
  237. def setUp(self):
  238. """Set up test fixtures"""
  239. super(TestDnscomProviderIntegration, self).setUp()
  240. self.id = "test_api_key"
  241. self.token = "test_api_secret"
  242. def test_full_workflow_create_new_record(self):
  243. """Test complete workflow for creating a new record"""
  244. provider = DnscomProvider(self.id, self.token)
  245. # Mock only the HTTP layer to simulate API responses
  246. with patch.object(provider, "_request") as mock_request:
  247. # Simulate API responses in order: zone query, record query, record creation
  248. mock_request.side_effect = [
  249. {"domainID": "example.com"}, # _query_zone_id response
  250. {"data": []}, # _query_record response (no existing record)
  251. {"recordID": "123456"}, # _create_record response
  252. ]
  253. result = provider.set_record("www.example.com", "1.2.3.4", "A", 300, "1")
  254. self.assertTrue(result)
  255. # Verify the actual API calls made
  256. self.assertEqual(mock_request.call_count, 3)
  257. mock_request.assert_any_call("domain/getsingle", domainID="example.com")
  258. mock_request.assert_any_call("record/list", domainID="example.com", host="www", pageSize=500)
  259. mock_request.assert_any_call(
  260. "record/create",
  261. domainID="example.com",
  262. value="1.2.3.4",
  263. host="www",
  264. type="A",
  265. TTL=300,
  266. viewID="1",
  267. remark="Managed by [DDNS](https://ddns.newfuture.cc)",
  268. )
  269. def test_full_workflow_update_existing_record(self):
  270. """Test complete workflow for updating an existing record"""
  271. provider = DnscomProvider(self.id, self.token)
  272. # Mock only the HTTP layer to simulate raw API responses
  273. with patch.object(provider, "_http") as mock_http:
  274. # Simulate raw HTTP API responses as they would come from the server
  275. mock_http.side_effect = [
  276. {"code": 0, "data": {"domainID": "example.com"}}, # domain/getsingle response
  277. {
  278. "code": 0,
  279. "data": { # record/list response
  280. "data": [{"record": "www", "type": "A", "recordID": "123456", "value": "5.6.7.8"}]
  281. },
  282. },
  283. {"code": 0, "data": {"recordID": "123456", "success": True}}, # record/modify response
  284. ]
  285. result = provider.set_record("www.example.com", "1.2.3.4", "A", 300, "1")
  286. self.assertTrue(result)
  287. # Verify the actual HTTP calls were made (3 calls total)
  288. self.assertEqual(mock_http.call_count, 3)
  289. def test_full_workflow_zone_not_found(self):
  290. """Test complete workflow when zone is not found"""
  291. provider = DnscomProvider(self.id, self.token)
  292. with patch.object(provider, "_request") as mock_request:
  293. # Simulate API returning None for zone query
  294. mock_request.return_value = None
  295. result = provider.set_record("www.nonexistent.com", "1.2.3.4", "A")
  296. self.assertFalse(result)
  297. def test_full_workflow_create_failure(self):
  298. """Test complete workflow when record creation fails"""
  299. provider = DnscomProvider(self.id, self.token)
  300. with patch.object(provider, "_request") as mock_request:
  301. # Simulate responses: zone found, no existing record, creation fails
  302. mock_request.side_effect = [
  303. {"domainID": "example.com"}, # _query_zone_id response
  304. {"data": []}, # _query_record response (no existing record)
  305. {"error": "Domain not found"}, # _create_record fails
  306. ]
  307. result = provider.set_record("www.example.com", "1.2.3.4", "A")
  308. self.assertFalse(result)
  309. def test_full_workflow_update_failure(self):
  310. """Test complete workflow when record update fails"""
  311. provider = DnscomProvider(self.id, self.token)
  312. with patch.object(provider, "_request") as mock_request:
  313. # Simulate responses: zone found, existing record found, update fails
  314. mock_request.side_effect = [
  315. {"domainID": "example.com"}, # _query_zone_id response
  316. { # _query_record response (existing record found)
  317. "data": [{"record": "www", "type": "A", "recordID": "123456", "value": "5.6.7.8"}]
  318. },
  319. None, # _update_record fails
  320. ]
  321. result = provider.set_record("www.example.com", "1.2.3.4", "A")
  322. self.assertFalse(result)
  323. if __name__ == "__main__":
  324. unittest.main()