test_provider_namecom.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. # coding=utf-8
  2. """
  3. Unit tests for NamecomProvider
  4. @author: GitHub Copilot
  5. """
  6. import base64
  7. from base_test import BaseProviderTestCase, patch, unittest
  8. from ddns.provider.namecom import NamecomProvider
  9. class TestNamecomProvider(BaseProviderTestCase):
  10. """Test cases for NamecomProvider"""
  11. def setUp(self):
  12. """Set up test fixtures"""
  13. super(TestNamecomProvider, self).setUp()
  14. self.id = "test_username"
  15. self.token = "test_api_token"
  16. def test_class_constants(self):
  17. """Test NamecomProvider class constants"""
  18. self.assertEqual(NamecomProvider.endpoint, "https://api.name.com")
  19. self.assertEqual(NamecomProvider.content_type, "application/json")
  20. self.assertTrue(NamecomProvider.decode_response)
  21. def test_init_with_basic_config(self):
  22. """Test NamecomProvider initialization with basic configuration"""
  23. provider = NamecomProvider(self.id, self.token)
  24. self.assertEqual(provider.id, self.id)
  25. self.assertEqual(provider.token, self.token)
  26. self.assertEqual(provider.endpoint, "https://api.name.com")
  27. def test_validate_success(self):
  28. """Test _validate method with valid credentials"""
  29. provider = NamecomProvider(self.id, self.token)
  30. # Should not raise any exception
  31. provider._validate()
  32. def test_validate_failure_no_id(self):
  33. """Test _validate method with missing id (username)"""
  34. with self.assertRaises(ValueError) as cm:
  35. NamecomProvider("", self.token)
  36. self.assertIn("id (username) must be configured", str(cm.exception))
  37. def test_validate_failure_no_token(self):
  38. """Test _validate method with missing token"""
  39. with self.assertRaises(ValueError) as cm:
  40. NamecomProvider(self.id, "")
  41. self.assertIn("token (API token) must be configured", str(cm.exception))
  42. def test_get_auth_header(self):
  43. """Test _get_auth_header generates correct Basic Auth header"""
  44. provider = NamecomProvider(self.id, self.token)
  45. auth_header = provider._get_auth_header()
  46. # Basic auth should be base64 encoded "username:token"
  47. expected = "Basic " + base64.b64encode("{}:{}".format(self.id, self.token).encode()).decode()
  48. self.assertEqual(auth_header, expected)
  49. def test_request_success(self):
  50. """Test _request method with successful response"""
  51. provider = NamecomProvider(self.id, self.token)
  52. with patch.object(provider, "_http") as mock_http:
  53. mock_http.return_value = {"records": [], "totalCount": 0}
  54. result = provider._request("GET", "/core/v1/domains/example.com/records")
  55. mock_http.assert_called_once()
  56. call_args = mock_http.call_args
  57. self.assertEqual(call_args[0][0], "GET")
  58. self.assertEqual(call_args[0][1], "/core/v1/domains/example.com/records")
  59. self.assertIn("Authorization", call_args[1]["headers"])
  60. self.assertTrue(call_args[1]["headers"]["Authorization"].startswith("Basic "))
  61. self.assertEqual(result, {"records": [], "totalCount": 0})
  62. def test_request_with_body(self):
  63. """Test _request method with body for POST/PUT"""
  64. provider = NamecomProvider(self.id, self.token)
  65. with patch.object(provider, "_http") as mock_http:
  66. mock_http.return_value = {"id": 123, "host": "www"}
  67. body = {"host": "www", "type": "A", "answer": "1.2.3.4"}
  68. provider._request("POST", "/core/v1/domains/example.com/records", body)
  69. mock_http.assert_called_once()
  70. call_args = mock_http.call_args
  71. self.assertEqual(call_args[0][0], "POST")
  72. self.assertEqual(call_args[1]["body"], body)
  73. def test_query_zone_id_success(self):
  74. """Test _query_zone_id method with successful response"""
  75. provider = NamecomProvider(self.id, self.token)
  76. with patch.object(provider, "_request") as mock_request:
  77. mock_request.return_value = {"records": [], "totalCount": 0}
  78. result = provider._query_zone_id("example.com")
  79. mock_request.assert_called_once_with("GET", "/core/v1/domains/example.com/records")
  80. self.assertEqual(result, "example.com")
  81. def test_query_zone_id_not_found(self):
  82. """Test _query_zone_id method when domain is not found"""
  83. provider = NamecomProvider(self.id, self.token)
  84. with patch.object(provider, "_request") as mock_request:
  85. mock_request.return_value = None
  86. result = provider._query_zone_id("notfound.com")
  87. self.assertIsNone(result)
  88. def test_query_zone_id_auth_error(self):
  89. """Test _query_zone_id method with authentication error"""
  90. provider = NamecomProvider(self.id, self.token)
  91. with patch.object(provider, "_request") as mock_request:
  92. mock_request.side_effect = RuntimeError("认证失败 [401]")
  93. with self.assertRaises(RuntimeError):
  94. provider._query_zone_id("example.com")
  95. def test_query_record_success(self):
  96. """Test _query_record method with successful response"""
  97. provider = NamecomProvider(self.id, self.token)
  98. with patch.object(provider, "_request") as mock_request:
  99. mock_request.return_value = {
  100. "records": [
  101. {"id": 123, "host": "www", "type": "A", "answer": "1.2.3.4", "ttl": 300},
  102. {"id": 124, "host": "mail", "type": "A", "answer": "5.6.7.8", "ttl": 300},
  103. ]
  104. }
  105. result = provider._query_record("example.com", "www", "example.com", "A", None, {})
  106. mock_request.assert_called_once_with("GET", "/core/v1/domains/example.com/records")
  107. self.assertIsNotNone(result)
  108. self.assertEqual(result["id"], 123)
  109. self.assertEqual(result["host"], "www")
  110. def test_query_record_not_found(self):
  111. """Test _query_record method when no matching record is found"""
  112. provider = NamecomProvider(self.id, self.token)
  113. with patch.object(provider, "_request") as mock_request:
  114. mock_request.return_value = {
  115. "records": [{"id": 124, "host": "mail", "type": "A", "answer": "5.6.7.8", "ttl": 300}]
  116. }
  117. result = provider._query_record("example.com", "www", "example.com", "A", None, {})
  118. self.assertIsNone(result)
  119. def test_query_record_type_mismatch(self):
  120. """Test _query_record method when record type doesn't match"""
  121. provider = NamecomProvider(self.id, self.token)
  122. with patch.object(provider, "_request") as mock_request:
  123. mock_request.return_value = {
  124. "records": [{"id": 123, "host": "www", "type": "CNAME", "answer": "other.com", "ttl": 300}]
  125. }
  126. result = provider._query_record("example.com", "www", "example.com", "A", None, {})
  127. self.assertIsNone(result)
  128. def test_query_record_root_domain(self):
  129. """Test _query_record method for root domain (@)"""
  130. provider = NamecomProvider(self.id, self.token)
  131. with patch.object(provider, "_request") as mock_request:
  132. mock_request.return_value = {
  133. "records": [
  134. {"id": 123, "host": "", "type": "A", "answer": "1.2.3.4", "ttl": 300},
  135. {"id": 124, "host": "www", "type": "A", "answer": "5.6.7.8", "ttl": 300},
  136. ]
  137. }
  138. result = provider._query_record("example.com", "@", "example.com", "A", None, {})
  139. self.assertIsNotNone(result)
  140. self.assertEqual(result["id"], 123)
  141. self.assertEqual(result["host"], "")
  142. def test_query_record_empty_response(self):
  143. """Test _query_record method with empty response"""
  144. provider = NamecomProvider(self.id, self.token)
  145. with patch.object(provider, "_request") as mock_request:
  146. mock_request.return_value = {"records": []}
  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 = NamecomProvider(self.id, self.token)
  152. with patch.object(provider, "_request") as mock_request:
  153. mock_request.return_value = {"id": 123, "host": "www", "type": "A", "answer": "1.2.3.4", "ttl": 300}
  154. result = provider._create_record("example.com", "www", "example.com", "1.2.3.4", "A", 300, None, {})
  155. mock_request.assert_called_once_with(
  156. "POST",
  157. "/core/v1/domains/example.com/records",
  158. {"host": "www", "type": "A", "answer": "1.2.3.4", "ttl": 300},
  159. )
  160. self.assertTrue(result)
  161. def test_create_record_failure(self):
  162. """Test _create_record method with failed creation"""
  163. provider = NamecomProvider(self.id, self.token)
  164. with patch.object(provider, "_request") as mock_request:
  165. mock_request.return_value = None
  166. result = provider._create_record("example.com", "www", "example.com", "1.2.3.4", "A", None, None, {})
  167. call_args = mock_request.call_args
  168. self.assertNotIn("ttl", call_args[0][2]) # TTL field should not be present when None
  169. self.assertFalse(result)
  170. def test_create_record_root_domain(self):
  171. """Test _create_record method for root domain"""
  172. provider = NamecomProvider(self.id, self.token)
  173. with patch.object(provider, "_request") as mock_request:
  174. mock_request.return_value = {"id": 123, "host": "", "type": "A", "answer": "1.2.3.4"}
  175. result = provider._create_record("example.com", "@", "example.com", "1.2.3.4", "A", None, None, {})
  176. call_args = mock_request.call_args
  177. self.assertEqual(call_args[0][2]["host"], "") # Empty string for root
  178. self.assertNotIn("ttl", call_args[0][2]) # TTL field should not be present when None
  179. self.assertTrue(result)
  180. def test_create_record_with_priority(self):
  181. """Test _create_record method with priority for MX records"""
  182. provider = NamecomProvider(self.id, self.token)
  183. with patch.object(provider, "_request") as mock_request:
  184. mock_request.return_value = {
  185. "id": 123,
  186. "host": "",
  187. "type": "MX",
  188. "answer": "mail.example.com",
  189. "priority": 10,
  190. }
  191. extra = {"priority": 10}
  192. result = provider._create_record(
  193. "example.com", "@", "example.com", "mail.example.com", "MX", 300, None, extra
  194. )
  195. call_args = mock_request.call_args
  196. self.assertEqual(call_args[0][2]["priority"], 10)
  197. self.assertTrue(result)
  198. def test_create_record_ttl_minimum(self):
  199. """Test _create_record method enforces minimum TTL of 300"""
  200. provider = NamecomProvider(self.id, self.token)
  201. with patch.object(provider, "_request") as mock_request:
  202. mock_request.return_value = {"id": 123}
  203. # Try to set TTL less than 300
  204. result = provider._create_record("example.com", "www", "example.com", "1.2.3.4", "A", 60, None, {})
  205. call_args = mock_request.call_args
  206. self.assertEqual(call_args[0][2]["ttl"], 300) # Should be enforced to minimum 300
  207. self.assertTrue(result)
  208. def test_create_record_ttl_zero(self):
  209. """Test _create_record method handles TTL of 0 correctly"""
  210. provider = NamecomProvider(self.id, self.token)
  211. with patch.object(provider, "_request") as mock_request:
  212. mock_request.return_value = {"id": 123}
  213. # TTL of 0 should be enforced to minimum 300
  214. result = provider._create_record("example.com", "www", "example.com", "1.2.3.4", "A", 0, None, {})
  215. call_args = mock_request.call_args
  216. self.assertEqual(call_args[0][2]["ttl"], 300) # Should be enforced to minimum 300
  217. self.assertTrue(result)
  218. def test_update_record_success(self):
  219. """Test _update_record method with successful update"""
  220. provider = NamecomProvider(self.id, self.token)
  221. old_record = {"id": 123, "host": "www", "type": "A", "answer": "1.2.3.4", "ttl": 300}
  222. with patch.object(provider, "_request") as mock_request:
  223. mock_request.return_value = {"id": 123, "host": "www", "type": "A", "answer": "5.6.7.8", "ttl": 600}
  224. result = provider._update_record("example.com", old_record, "5.6.7.8", "A", 600, None, {})
  225. mock_request.assert_called_once_with(
  226. "PUT",
  227. "/core/v1/domains/example.com/records/123",
  228. {"host": "www", "type": "A", "answer": "5.6.7.8", "ttl": 600},
  229. )
  230. self.assertTrue(result)
  231. def test_update_record_failure(self):
  232. """Test _update_record method with failed update"""
  233. provider = NamecomProvider(self.id, self.token)
  234. old_record = {"id": 123, "host": "www"}
  235. with patch.object(provider, "_request") as mock_request:
  236. mock_request.return_value = None
  237. result = provider._update_record("example.com", old_record, "5.6.7.8", "A", None, None, {})
  238. self.assertFalse(result)
  239. def test_update_record_preserves_ttl(self):
  240. """Test _update_record method preserves existing TTL if not specified"""
  241. provider = NamecomProvider(self.id, self.token)
  242. old_record = {"id": 123, "host": "www", "type": "A", "answer": "1.2.3.4", "ttl": 600}
  243. with patch.object(provider, "_request") as mock_request:
  244. mock_request.return_value = {"id": 123}
  245. result = provider._update_record("example.com", old_record, "5.6.7.8", "A", None, None, {})
  246. call_args = mock_request.call_args
  247. self.assertEqual(call_args[0][2]["ttl"], 600) # Preserved from old_record
  248. self.assertTrue(result)
  249. def test_update_record_preserves_priority(self):
  250. """Test _update_record method preserves existing priority if not specified"""
  251. provider = NamecomProvider(self.id, self.token)
  252. old_record = {"id": 123, "host": "", "type": "MX", "answer": "mail.example.com", "ttl": 300, "priority": 10}
  253. with patch.object(provider, "_request") as mock_request:
  254. mock_request.return_value = {"id": 123}
  255. result = provider._update_record("example.com", old_record, "mail2.example.com", "MX", None, None, {})
  256. call_args = mock_request.call_args
  257. self.assertEqual(call_args[0][2]["priority"], 10) # Preserved from old_record
  258. self.assertTrue(result)
  259. def test_update_record_override_priority(self):
  260. """Test _update_record method allows overriding priority via extra"""
  261. provider = NamecomProvider(self.id, self.token)
  262. old_record = {"id": 123, "host": "", "type": "MX", "answer": "mail.example.com", "priority": 10}
  263. with patch.object(provider, "_request") as mock_request:
  264. mock_request.return_value = {"id": 123}
  265. extra = {"priority": 20}
  266. result = provider._update_record("example.com", old_record, "mail2.example.com", "MX", None, None, extra)
  267. call_args = mock_request.call_args
  268. self.assertEqual(call_args[0][2]["priority"], 20) # Overridden by extra
  269. self.assertTrue(result)
  270. def test_update_record_no_id(self):
  271. """Test _update_record method fails if old_record has no id"""
  272. provider = NamecomProvider(self.id, self.token)
  273. old_record = {"host": "www", "type": "A"} # Missing id
  274. with patch.object(provider, "_request") as mock_request:
  275. result = provider._update_record("example.com", old_record, "5.6.7.8", "A", None, None, {})
  276. mock_request.assert_not_called()
  277. self.assertFalse(result)
  278. class TestNamecomProviderIntegration(BaseProviderTestCase):
  279. """Integration test cases for NamecomProvider - testing with minimal mocking"""
  280. def setUp(self):
  281. """Set up test fixtures"""
  282. super(TestNamecomProviderIntegration, self).setUp()
  283. self.id = "test_username"
  284. self.token = "test_api_token"
  285. def test_full_workflow_create_new_record(self):
  286. """Test complete workflow for creating a new record"""
  287. provider = NamecomProvider(self.id, self.token)
  288. with patch.object(provider, "_request") as mock_request:
  289. # Simulate API responses: zone query, record query, record creation
  290. mock_request.side_effect = [
  291. {"records": [], "totalCount": 0}, # _query_zone_id response
  292. {"records": []}, # _query_record response (no existing record)
  293. {"id": 123, "host": "www", "type": "A", "answer": "1.2.3.4"}, # _create_record response
  294. ]
  295. result = provider.set_record("www.example.com", "1.2.3.4", "A", 300)
  296. self.assertTrue(result)
  297. self.assertEqual(mock_request.call_count, 3)
  298. def test_full_workflow_update_existing_record(self):
  299. """Test complete workflow for updating an existing record"""
  300. provider = NamecomProvider(self.id, self.token)
  301. with patch.object(provider, "_request") as mock_request:
  302. # Simulate API responses: zone query, record query, record update
  303. mock_request.side_effect = [
  304. {"records": []}, # _query_zone_id response
  305. {
  306. "records": [{"id": 123, "host": "www", "type": "A", "answer": "1.2.3.4", "ttl": 300}]
  307. }, # _query_record
  308. {"id": 123, "host": "www", "type": "A", "answer": "5.6.7.8"}, # _update_record response
  309. ]
  310. result = provider.set_record("www.example.com", "5.6.7.8", "A", 600)
  311. self.assertTrue(result)
  312. self.assertEqual(mock_request.call_count, 3)
  313. def test_full_workflow_zone_not_found(self):
  314. """Test complete workflow when zone is not found"""
  315. provider = NamecomProvider(self.id, self.token)
  316. with patch.object(provider, "_request") as mock_request:
  317. # Simulate zone not found
  318. mock_request.return_value = None
  319. result = provider.set_record("www.nonexistent.com", "1.2.3.4", "A")
  320. self.assertFalse(result)
  321. def test_full_workflow_create_failure(self):
  322. """Test complete workflow when record creation fails"""
  323. provider = NamecomProvider(self.id, self.token)
  324. with patch.object(provider, "_request") as mock_request:
  325. mock_request.side_effect = [
  326. {"records": []}, # _query_zone_id response
  327. {"records": []}, # _query_record response (no existing record)
  328. None, # _create_record fails
  329. ]
  330. result = provider.set_record("www.example.com", "1.2.3.4", "A")
  331. self.assertFalse(result)
  332. def test_full_workflow_update_failure(self):
  333. """Test complete workflow when record update fails"""
  334. provider = NamecomProvider(self.id, self.token)
  335. with patch.object(provider, "_request") as mock_request:
  336. mock_request.side_effect = [
  337. {"records": []}, # _query_zone_id response
  338. {"records": [{"id": 123, "host": "www", "type": "A", "answer": "1.2.3.4"}]}, # _query_record
  339. None, # _update_record fails
  340. ]
  341. result = provider.set_record("www.example.com", "5.6.7.8", "A")
  342. self.assertFalse(result)
  343. def test_full_workflow_auth_error(self):
  344. """Test complete workflow when authentication fails"""
  345. provider = NamecomProvider(self.id, self.token)
  346. with patch.object(provider, "_request") as mock_request:
  347. mock_request.side_effect = RuntimeError("认证失败 [401]")
  348. result = provider.set_record("www.example.com", "1.2.3.4", "A")
  349. self.assertFalse(result)
  350. def test_full_workflow_ipv6_record(self):
  351. """Test complete workflow for IPv6 AAAA record"""
  352. provider = NamecomProvider(self.id, self.token)
  353. with patch.object(provider, "_request") as mock_request:
  354. mock_request.side_effect = [
  355. {"records": []}, # _query_zone_id response
  356. {"records": []}, # _query_record response (no existing record)
  357. {"id": 123, "host": "www", "type": "AAAA", "answer": "2001:db8::1"}, # _create_record response
  358. ]
  359. result = provider.set_record("www.example.com", "2001:db8::1", "AAAA", 300)
  360. self.assertTrue(result)
  361. # Verify AAAA type was used
  362. create_call = mock_request.call_args_list[2]
  363. self.assertEqual(create_call[0][2]["type"], "AAAA")
  364. def test_full_workflow_root_domain(self):
  365. """Test complete workflow for root domain record"""
  366. provider = NamecomProvider(self.id, self.token)
  367. with patch.object(provider, "_request") as mock_request:
  368. mock_request.side_effect = [
  369. {"records": []}, # _query_zone_id response
  370. {"records": []}, # _query_record response (no existing record)
  371. {"id": 123, "host": "", "type": "A", "answer": "1.2.3.4"}, # _create_record response
  372. ]
  373. result = provider.set_record("example.com", "1.2.3.4", "A", 300)
  374. self.assertTrue(result)
  375. # Verify host is empty string for root domain
  376. create_call = mock_request.call_args_list[2]
  377. self.assertEqual(create_call[0][2]["host"], "")
  378. def test_custom_endpoint(self):
  379. """Test provider with custom endpoint (e.g., sandbox)"""
  380. sandbox_endpoint = "https://api.dev.name.com"
  381. provider = NamecomProvider(self.id, self.token, endpoint=sandbox_endpoint)
  382. self.assertEqual(provider.endpoint, sandbox_endpoint)
  383. if __name__ == "__main__":
  384. unittest.main()