test_provider_huaweidns.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. # coding=utf-8
  2. """
  3. Unit tests for HuaweiDNSProvider
  4. @author: GitHub Copilot
  5. """
  6. from base_test import BaseProviderTestCase, unittest, patch
  7. from ddns.provider.huaweidns import HuaweiDNSProvider
  8. class TestHuaweiDNSProvider(BaseProviderTestCase):
  9. """Test cases for HuaweiDNSProvider"""
  10. def setUp(self):
  11. """Set up test fixtures"""
  12. super(TestHuaweiDNSProvider, self).setUp()
  13. self.auth_id = "test_access_key"
  14. self.auth_token = "test_secret_key"
  15. self.provider = HuaweiDNSProvider(self.auth_id, self.auth_token)
  16. # Mock strftime for all tests
  17. self.strftime_patcher = patch("ddns.provider.huaweidns.strftime")
  18. self.mock_strftime = self.strftime_patcher.start()
  19. self.mock_strftime.return_value = "20230101T120000Z"
  20. def tearDown(self):
  21. """Clean up test fixtures"""
  22. self.strftime_patcher.stop()
  23. super(TestHuaweiDNSProvider, self).tearDown()
  24. def test_class_constants(self):
  25. """Test HuaweiDNSProvider class constants"""
  26. self.assertEqual(HuaweiDNSProvider.API, "https://dns.myhuaweicloud.com")
  27. self.assertEqual(HuaweiDNSProvider.content_type, "application/json")
  28. self.assertTrue(HuaweiDNSProvider.decode_response)
  29. self.assertEqual(HuaweiDNSProvider.algorithm, "SDK-HMAC-SHA256")
  30. def test_init_with_basic_config(self):
  31. """Test HuaweiDNSProvider initialization with basic configuration"""
  32. self.assertEqual(self.provider.auth_id, self.auth_id)
  33. self.assertEqual(self.provider.auth_token, self.auth_token)
  34. self.assertEqual(self.provider.API, "https://dns.myhuaweicloud.com")
  35. def test_request_get_method(self):
  36. """Test _request method with GET method"""
  37. with patch.object(self.provider, "_http") as mock_http:
  38. mock_http.return_value = {"zones": []}
  39. result = self.provider._request("GET", "/v2/zones", name="example.com", limit=500)
  40. mock_http.assert_called_once()
  41. self.assertEqual(result, {"zones": []})
  42. def test_request_post_method(self):
  43. """Test _request method with POST method"""
  44. with patch.object(self.provider, "_http") as mock_http:
  45. mock_http.return_value = {"id": "record123"}
  46. result = self.provider._request(
  47. "POST", "/v2.1/zones/zone123/recordsets", name="www.example.com", type="A", records=["1.2.3.4"]
  48. )
  49. mock_http.assert_called_once()
  50. self.assertEqual(result, {"id": "record123"})
  51. def test_request_filters_none_params(self):
  52. """Test _request method filters out None parameters"""
  53. with patch.object(self.provider, "_http") as mock_http:
  54. mock_http.return_value = {"zones": []}
  55. self.provider._request("GET", "/v2/zones", name="example.com", limit=None, type=None)
  56. # Verify that _http was called (None params should be filtered)
  57. mock_http.assert_called_once()
  58. def test_query_zone_id_success(self):
  59. """Test _query_zone_id method with successful response"""
  60. with patch.object(self.provider, "_request") as mock_request:
  61. mock_request.return_value = {
  62. "zones": [{"id": "zone123", "name": "example.com."}, {"id": "zone456", "name": "another.com."}]
  63. }
  64. result = self.provider._query_zone_id("example.com")
  65. mock_request.assert_called_once_with(
  66. "GET", "/v2/zones", search_mode="equal", limit=500, name="example.com."
  67. )
  68. self.assertEqual(result, "zone123")
  69. def test_query_zone_id_with_trailing_dot(self):
  70. """Test _query_zone_id method with domain already having trailing dot"""
  71. with patch.object(self.provider, "_request") as mock_request:
  72. mock_request.return_value = {"zones": [{"id": "zone123", "name": "example.com."}]}
  73. result = self.provider._query_zone_id("example.com.")
  74. mock_request.assert_called_once_with(
  75. "GET", "/v2/zones", search_mode="equal", limit=500, name="example.com."
  76. )
  77. self.assertEqual(result, "zone123")
  78. def test_query_zone_id_not_found(self):
  79. """Test _query_zone_id method when domain is not found"""
  80. with patch.object(self.provider, "_request") as mock_request:
  81. mock_request.return_value = {"zones": []}
  82. result = self.provider._query_zone_id("notfound.com")
  83. self.assertIsNone(result)
  84. def test_query_record_success(self):
  85. """Test _query_record method with successful response"""
  86. with patch.object(self.provider, "_request") as mock_request:
  87. mock_request.return_value = {
  88. "recordsets": [
  89. {"id": "rec123", "name": "www.example.com.", "type": "A", "records": ["1.2.3.4"]},
  90. {"id": "rec456", "name": "mail.example.com.", "type": "A", "records": ["5.6.7.8"]},
  91. ]
  92. }
  93. result = self.provider._query_record("zone123", "www", "example.com", "A", None, {})
  94. mock_request.assert_called_once_with(
  95. "GET",
  96. "/v2.1/zones/zone123/recordsets",
  97. limit=500,
  98. name="www.example.com.",
  99. type="A",
  100. line_id=None,
  101. search_mode="equal",
  102. )
  103. self.assertIsNotNone(result)
  104. if result: # Type narrowing
  105. self.assertEqual(result["id"], "rec123")
  106. self.assertEqual(result["name"], "www.example.com.")
  107. def test_query_record_with_line(self):
  108. """Test _query_record method with line parameter"""
  109. with patch.object(self.provider, "_request") as mock_request:
  110. mock_request.return_value = {"recordsets": []}
  111. self.provider._query_record("zone123", "www", "example.com", "A", "line1", {})
  112. mock_request.assert_called_once_with(
  113. "GET",
  114. "/v2.1/zones/zone123/recordsets",
  115. limit=500,
  116. name="www.example.com.",
  117. type="A",
  118. line_id="line1",
  119. search_mode="equal",
  120. )
  121. def test_query_record_not_found(self):
  122. """Test _query_record method when no matching record is found"""
  123. with patch.object(self.provider, "_request") as mock_request:
  124. mock_request.return_value = {
  125. "recordsets": [{"id": "rec456", "name": "mail.example.com.", "type": "A", "records": ["5.6.7.8"]}]
  126. }
  127. result = self.provider._query_record("zone123", "www", "example.com", "A", None, {})
  128. self.assertIsNone(result)
  129. def test_create_record_success(self):
  130. """Test _create_record method with successful creation"""
  131. with patch.object(self.provider, "_request") as mock_request:
  132. mock_request.return_value = {"id": "rec123456"}
  133. result = self.provider._create_record("zone123", "www", "example.com", "1.2.3.4", "A", 300, "line1", {})
  134. mock_request.assert_called_once_with(
  135. "POST",
  136. "/v2.1/zones/zone123/recordsets",
  137. name="www.example.com.",
  138. type="A",
  139. records=["1.2.3.4"],
  140. ttl=300,
  141. line="line1",
  142. description=self.provider.remark,
  143. )
  144. self.assertTrue(result)
  145. def test_create_record_with_extra_params(self):
  146. """Test _create_record method with extra parameters"""
  147. with patch.object(self.provider, "_request") as mock_request:
  148. mock_request.return_value = {"id": "rec123456"}
  149. extra = {"description": "Custom description", "tags": ["tag1", "tag2"]}
  150. result = self.provider._create_record("zone123", "www", "example.com", "1.2.3.4", "A", 300, None, extra)
  151. mock_request.assert_called_once_with(
  152. "POST",
  153. "/v2.1/zones/zone123/recordsets",
  154. name="www.example.com.",
  155. type="A",
  156. records=["1.2.3.4"],
  157. ttl=300,
  158. line=None,
  159. description="Custom description",
  160. tags=["tag1", "tag2"],
  161. )
  162. self.assertTrue(result)
  163. def test_create_record_failure(self):
  164. """Test _create_record method with failed creation"""
  165. provider = HuaweiDNSProvider(self.auth_id, self.auth_token)
  166. with patch.object(provider, "_request") as mock_request:
  167. mock_request.return_value = {"error": "Zone not found"}
  168. result = provider._create_record("zone123", "www", "example.com", "1.2.3.4", "A", None, None, {})
  169. self.assertFalse(result)
  170. def test_update_record_success(self):
  171. """Test _update_record method with successful update"""
  172. provider = HuaweiDNSProvider(self.auth_id, self.auth_token)
  173. old_record = {"id": "rec123", "name": "www.example.com.", "ttl": 300}
  174. with patch.object(provider, "_request") as mock_request:
  175. mock_request.return_value = {"id": "rec123"}
  176. result = provider._update_record("zone123", old_record, "5.6.7.8", "A", 600, None, {})
  177. mock_request.assert_called_once_with(
  178. "PUT",
  179. "/v2.1/zones/zone123/recordsets/rec123",
  180. name="www.example.com.",
  181. type="A",
  182. records=["5.6.7.8"],
  183. ttl=600,
  184. description=provider.remark,
  185. )
  186. self.assertTrue(result)
  187. def test_update_record_with_fallback_ttl(self):
  188. """Test _update_record method uses old record's TTL when ttl is None"""
  189. provider = HuaweiDNSProvider(self.auth_id, self.auth_token)
  190. old_record = {"id": "rec123", "name": "www.example.com.", "ttl": 300}
  191. with patch.object(provider, "_request") as mock_request:
  192. mock_request.return_value = {"id": "rec123"}
  193. result = provider._update_record("zone123", old_record, "5.6.7.8", "A", None, None, {})
  194. mock_request.assert_called_once_with(
  195. "PUT",
  196. "/v2.1/zones/zone123/recordsets/rec123",
  197. name="www.example.com.",
  198. type="A",
  199. records=["5.6.7.8"],
  200. ttl=300,
  201. description=provider.remark,
  202. )
  203. self.assertTrue(result)
  204. def test_update_record_with_extra_params(self):
  205. """Test _update_record method with extra parameters"""
  206. provider = HuaweiDNSProvider(self.auth_id, self.auth_token)
  207. old_record = {"id": "rec123", "name": "www.example.com.", "ttl": 300}
  208. with patch.object(provider, "_request") as mock_request:
  209. mock_request.return_value = {"id": "rec123"}
  210. extra = {"description": "Updated description", "tags": ["newtag"]}
  211. result = provider._update_record("zone123", old_record, "5.6.7.8", "A", 600, "line2", extra)
  212. mock_request.assert_called_once_with(
  213. "PUT",
  214. "/v2.1/zones/zone123/recordsets/rec123",
  215. name="www.example.com.",
  216. type="A",
  217. records=["5.6.7.8"],
  218. ttl=600,
  219. description="Updated description",
  220. tags=["newtag"],
  221. )
  222. self.assertTrue(result)
  223. def test_update_record_failure(self):
  224. """Test _update_record method with failed update"""
  225. provider = HuaweiDNSProvider(self.auth_id, self.auth_token)
  226. old_record = {"id": "rec123", "name": "www.example.com."}
  227. with patch.object(provider, "_request") as mock_request:
  228. mock_request.return_value = {"error": "Record not found"}
  229. result = provider._update_record("zone123", old_record, "5.6.7.8", "A", None, None, {})
  230. self.assertFalse(result)
  231. def test_line_configuration_support(self):
  232. """Test that HuaweiDNSProvider supports line configuration"""
  233. provider = HuaweiDNSProvider(self.auth_id, self.auth_token)
  234. with patch.object(provider, "_request") as mock_request:
  235. mock_request.return_value = {"id": "rec123456"}
  236. # Test create record with line parameter (line is passed as extra parameter for Huawei)
  237. result = provider._create_record("zone123", "www", "example.com", "1.2.3.4", "A", 300, "telecom", {})
  238. # For Huawei DNS, line can be passed as extra parameter
  239. self.assertTrue(result)
  240. mock_request.assert_called_once()
  241. def test_update_record_with_line(self):
  242. """Test _update_record method with line parameter"""
  243. provider = HuaweiDNSProvider(self.auth_id, self.auth_token)
  244. old_record = {"id": "rec123", "name": "www.example.com."}
  245. with patch.object(provider, "_request") as mock_request:
  246. mock_request.return_value = {"id": "rec123"}
  247. # Test with line parameter (line is handled as needed for different DNS providers)
  248. result = provider._update_record("zone123", old_record, "5.6.7.8", "A", 600, "unicom", {})
  249. self.assertTrue(result)
  250. mock_request.assert_called_once()
  251. class TestHuaweiDNSProviderIntegration(BaseProviderTestCase):
  252. """Integration test cases for HuaweiDNSProvider - testing with minimal mocking"""
  253. def setUp(self):
  254. """Set up test fixtures"""
  255. super(TestHuaweiDNSProviderIntegration, self).setUp()
  256. self.auth_id = "test_access_key"
  257. self.auth_token = "test_secret_key"
  258. def test_full_workflow_create_new_record(self):
  259. """Test complete workflow for creating a new record"""
  260. provider = HuaweiDNSProvider(self.auth_id, self.auth_token)
  261. # Mock only the HTTP layer to simulate API responses
  262. with patch.object(provider, "_request") as mock_request:
  263. # Simulate API responses in order: zone query, record query, record creation
  264. mock_request.side_effect = [
  265. {"zones": [{"id": "zone123", "name": "example.com."}]}, # _query_zone_id response
  266. {"recordsets": []}, # _query_record response (no existing record)
  267. {"id": "rec123456"}, # _create_record response
  268. ]
  269. result = provider.set_record("www.example.com", "1.2.3.4", "A", 300, "line1")
  270. self.assertTrue(result)
  271. # Verify the actual API calls made
  272. self.assertEqual(mock_request.call_count, 3)
  273. mock_request.assert_any_call("GET", "/v2/zones", search_mode="equal", limit=500, name="example.com.")
  274. mock_request.assert_any_call(
  275. "GET",
  276. "/v2.1/zones/zone123/recordsets",
  277. limit=500,
  278. name="www.example.com.",
  279. type="A",
  280. line_id="line1",
  281. search_mode="equal",
  282. )
  283. mock_request.assert_any_call(
  284. "POST",
  285. "/v2.1/zones/zone123/recordsets",
  286. name="www.example.com.",
  287. type="A",
  288. records=["1.2.3.4"],
  289. ttl=300,
  290. line="line1",
  291. description="Managed by [DDNS v0.0.0](https://ddns.newfuture.cc)",
  292. )
  293. def test_full_workflow_update_existing_record(self):
  294. """Test complete workflow for updating an existing record"""
  295. provider = HuaweiDNSProvider(self.auth_id, self.auth_token)
  296. with patch.object(provider, "_request") as mock_request:
  297. # Simulate API responses
  298. mock_request.side_effect = [
  299. {"zones": [{"id": "zone123", "name": "example.com."}]}, # _query_zone_id response
  300. { # _query_record response (existing record found)
  301. "recordsets": [
  302. {"id": "rec123", "name": "www.example.com.", "type": "A", "records": ["5.6.7.8"], "ttl": 300}
  303. ]
  304. },
  305. {"id": "rec123"}, # _update_record response
  306. ]
  307. result = provider.set_record("www.example.com", "1.2.3.4", "A", 300, "line1")
  308. self.assertTrue(result)
  309. # Verify the update call was made
  310. mock_request.assert_any_call(
  311. "PUT",
  312. "/v2.1/zones/zone123/recordsets/rec123",
  313. name="www.example.com.",
  314. type="A",
  315. records=["1.2.3.4"],
  316. ttl=300,
  317. description="Managed by [DDNS v0.0.0](https://ddns.newfuture.cc)",
  318. )
  319. def test_full_workflow_zone_not_found(self):
  320. """Test complete workflow when zone is not found"""
  321. provider = HuaweiDNSProvider(self.auth_id, self.auth_token)
  322. with patch.object(provider, "_request") as mock_request:
  323. # Simulate API returning empty zones array
  324. mock_request.return_value = {"zones": []}
  325. result = provider.set_record("www.nonexistent.com", "1.2.3.4", "A")
  326. self.assertFalse(result)
  327. def test_full_workflow_create_failure(self):
  328. """Test complete workflow when record creation fails"""
  329. provider = HuaweiDNSProvider(self.auth_id, self.auth_token)
  330. with patch.object(provider, "_request") as mock_request:
  331. # Simulate responses: zone found, no existing record, creation fails
  332. mock_request.side_effect = [
  333. {"zones": [{"id": "zone123", "name": "example.com."}]}, # _query_zone_id response
  334. {"recordsets": []}, # _query_record response (no existing record)
  335. {"error": "Zone not found"}, # _create_record fails
  336. ]
  337. result = provider.set_record("www.example.com", "1.2.3.4", "A")
  338. self.assertFalse(result)
  339. def test_full_workflow_update_failure(self):
  340. """Test complete workflow when record update fails"""
  341. provider = HuaweiDNSProvider(self.auth_id, self.auth_token)
  342. with patch.object(provider, "_request") as mock_request:
  343. # Simulate responses: zone found, existing record found, update fails
  344. mock_request.side_effect = [
  345. {"zones": [{"id": "zone123", "name": "example.com."}]}, # _query_zone_id response
  346. { # _query_record response (existing record found)
  347. "recordsets": [
  348. {"id": "rec123", "name": "www.example.com.", "type": "A", "records": ["5.6.7.8"], "ttl": 300}
  349. ]
  350. },
  351. {"error": "Update failed"}, # _update_record fails
  352. ]
  353. result = provider.set_record("www.example.com", "1.2.3.4", "A")
  354. self.assertFalse(result)
  355. def test_full_workflow_with_extra_options(self):
  356. """Test complete workflow with additional options"""
  357. provider = HuaweiDNSProvider(self.auth_id, self.auth_token)
  358. with patch.object(provider, "_request") as mock_request:
  359. # Simulate successful creation with custom options
  360. mock_request.side_effect = [
  361. {"zones": [{"id": "zone123", "name": "example.com."}]}, # _query_zone_id response
  362. {"recordsets": []}, # _query_record response (no existing record)
  363. {"id": "rec123456"}, # _create_record response
  364. ]
  365. result = provider.set_record(
  366. "www.example.com", "1.2.3.4", "A", 600, "line2", description="Custom record", tags=["production"]
  367. )
  368. self.assertTrue(result)
  369. # Verify that extra parameters are passed through correctly
  370. mock_request.assert_any_call(
  371. "POST",
  372. "/v2.1/zones/zone123/recordsets",
  373. name="www.example.com.",
  374. type="A",
  375. records=["1.2.3.4"],
  376. ttl=600,
  377. line="line2",
  378. description="Custom record",
  379. tags=["production"],
  380. )
  381. if __name__ == "__main__":
  382. unittest.main()