test_provider_cloudflare.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  1. # coding=utf-8
  2. """
  3. Unit tests for CloudflareProvider
  4. @author: GitHub Copilot
  5. """
  6. from base_test import BaseProviderTestCase, patch, unittest
  7. from ddns.provider.cloudflare import CloudflareProvider
  8. class TestCloudflareProvider(BaseProviderTestCase):
  9. """Test cases for CloudflareProvider"""
  10. def setUp(self):
  11. """Set up test fixtures"""
  12. super(TestCloudflareProvider, self).setUp()
  13. self.id = "[email protected]"
  14. self.token = "test_api_key_or_token"
  15. def test_class_constants(self):
  16. """Test CloudflareProvider class constants"""
  17. self.assertEqual(CloudflareProvider.endpoint, "https://api.cloudflare.com")
  18. self.assertEqual(CloudflareProvider.content_type, "application/json")
  19. self.assertTrue(CloudflareProvider.decode_response)
  20. def test_init_with_basic_config(self):
  21. """Test CloudflareProvider initialization with basic configuration"""
  22. provider = CloudflareProvider(self.id, self.token)
  23. self.assertEqual(provider.id, self.id)
  24. self.assertEqual(provider.token, self.token)
  25. self.assertEqual(provider.endpoint, "https://api.cloudflare.com")
  26. def test_init_with_token_only(self):
  27. """Test CloudflareProvider initialization with token only (Bearer auth)"""
  28. provider = CloudflareProvider("", self.token)
  29. self.assertEqual(provider.id, "")
  30. self.assertEqual(provider.token, self.token)
  31. def test_validate_success_with_email(self):
  32. """Test _validate method with valid email"""
  33. provider = CloudflareProvider(self.id, self.token)
  34. # Should not raise any exception
  35. provider._validate()
  36. def test_validate_success_with_token_only(self):
  37. """Test _validate method with token only (no email)"""
  38. provider = CloudflareProvider("", self.token)
  39. # Should not raise any exception
  40. provider._validate()
  41. def test_validate_failure_no_token(self):
  42. """Test _validate method with missing token"""
  43. with self.assertRaises(ValueError) as cm:
  44. CloudflareProvider(self.id, "")
  45. self.assertIn("token must be configured", str(cm.exception))
  46. def test_validate_failure_invalid_email(self):
  47. """Test _validate method with invalid email format"""
  48. with self.assertRaises(ValueError) as cm:
  49. CloudflareProvider("invalid_email", self.token)
  50. self.assertIn("ID must be a valid email or Empty", str(cm.exception))
  51. def test_request_with_email_auth(self):
  52. """Test _request method using email + API key authentication"""
  53. provider = CloudflareProvider(self.id, self.token)
  54. with patch.object(provider, "_http") as mock_http:
  55. mock_http.return_value = {"success": True, "result": {"id": "zone123"}}
  56. result = provider._request("GET", "/test", param1="value1")
  57. mock_http.assert_called_once_with(
  58. "GET",
  59. "/client/v4/zones/test",
  60. headers={"X-Auth-Email": self.id, "X-Auth-Key": self.token},
  61. params={"param1": "value1"},
  62. )
  63. self.assertEqual(result, {"id": "zone123"})
  64. def test_request_with_bearer_auth(self):
  65. """Test _request method using Bearer token authentication"""
  66. provider = CloudflareProvider("", self.token)
  67. with patch.object(provider, "_http") as mock_http:
  68. mock_http.return_value = {"success": True, "result": {"id": "zone123"}}
  69. result = provider._request("GET", "/test", param1="value1")
  70. mock_http.assert_called_once_with(
  71. "GET",
  72. "/client/v4/zones/test",
  73. headers={"Authorization": "Bearer " + self.token},
  74. params={"param1": "value1"},
  75. )
  76. self.assertEqual(result, {"id": "zone123"})
  77. def test_request_failure(self):
  78. """Test _request method with failed response"""
  79. provider = CloudflareProvider(self.id, self.token)
  80. with patch.object(provider, "_http") as mock_http:
  81. mock_http.return_value = {"success": False, "errors": ["Invalid API key"]}
  82. result = provider._request("GET", "/test")
  83. self.assertEqual(result, {"success": False, "errors": ["Invalid API key"]})
  84. def test_request_filters_none_params(self):
  85. """Test _request method filters out None parameters"""
  86. provider = CloudflareProvider(self.id, self.token)
  87. with patch.object(provider, "_http") as mock_http:
  88. mock_http.return_value = {"success": True, "result": {}}
  89. provider._request("GET", "/test", param1="value1", param2=None, param3="value3")
  90. # Verify None parameters were filtered out
  91. call_args = mock_http.call_args
  92. params = call_args[1]["params"]
  93. self.assertEqual(params, {"param1": "value1", "param3": "value3"})
  94. def test_query_zone_id_success(self):
  95. """Test _query_zone_id method with successful response"""
  96. provider = CloudflareProvider(self.id, self.token)
  97. with patch.object(provider, "_request") as mock_request:
  98. mock_request.return_value = [
  99. {"id": "zone123", "name": "example.com"},
  100. {"id": "zone456", "name": "other.com"},
  101. ]
  102. result = provider._query_zone_id("example.com")
  103. mock_request.assert_called_once_with("GET", "", **{"name.exact": "example.com", "per_page": 50})
  104. self.assertEqual(result, "zone123")
  105. def test_query_zone_id_not_found(self):
  106. """Test _query_zone_id method when domain is not found"""
  107. provider = CloudflareProvider(self.id, self.token)
  108. with patch.object(provider, "_request") as mock_request:
  109. mock_request.return_value = [{"id": "zone456", "name": "other.com"}]
  110. result = provider._query_zone_id("notfound.com")
  111. self.assertIsNone(result)
  112. def test_query_zone_id_empty_response(self):
  113. """Test _query_zone_id method with empty response"""
  114. provider = CloudflareProvider(self.id, self.token)
  115. with patch.object(provider, "_request") as mock_request:
  116. mock_request.return_value = []
  117. result = provider._query_zone_id("example.com")
  118. self.assertIsNone(result)
  119. def test_query_record_success(self):
  120. """Test _query_record method with successful response"""
  121. provider = CloudflareProvider(self.id, self.token)
  122. with patch.object(provider, "_request") as mock_request:
  123. mock_request.return_value = [
  124. {"id": "rec123", "name": "www.example.com", "type": "A", "content": "1.2.3.4"},
  125. {"id": "rec456", "name": "mail.example.com", "type": "A", "content": "5.6.7.8"},
  126. ]
  127. res = provider._query_record("zone123", "www", "example.com", "A", None, {}) # type: dict # type: ignore
  128. self.assertEqual(res["id"], "rec123")
  129. self.assertEqual(res["name"], "www.example.com")
  130. params = {"name.exact": "www.example.com"}
  131. mock_request.assert_called_once_with("GET", "/zone123/dns_records", type="A", per_page=10000, **params)
  132. def test_query_record_not_found(self):
  133. """Test _query_record method when no matching record is found"""
  134. provider = CloudflareProvider(self.id, self.token)
  135. with patch("ddns.provider.cloudflare.join_domain", autospec=True) as mock_join, patch.object(
  136. provider, "_request", autospec=True
  137. ) as mock_request:
  138. mock_join.return_value = "www.example.com"
  139. mock_request.return_value = [
  140. {"id": "rec456", "name": "mail.example.com", "type": "A", "content": "5.6.7.8"}
  141. ]
  142. result = provider._query_record("zone123", "www", "example.com", "A", None, {})
  143. self.assertIsNone(result)
  144. def test_query_record_with_proxy_option(self):
  145. """Test _query_record method with proxy option in extra parameters"""
  146. provider = CloudflareProvider(self.id, self.token)
  147. with patch("ddns.provider.cloudflare.join_domain") as mock_join, patch.object(
  148. provider, "_request"
  149. ) as mock_request:
  150. mock_join.return_value = "www.example.com"
  151. # When record is found with extra filter, should not fallback
  152. mock_request.return_value = [
  153. {"id": "rec123", "name": "www.example.com", "type": "A", "content": "1.2.3.4", "proxied": True}
  154. ]
  155. extra = {"proxied": True}
  156. result = provider._query_record("zone123", "www", "example.com", "A", None, extra)
  157. # Should call only once since record is found with extra filter
  158. # Note: proxied is converted to lowercase string "true"
  159. mock_request.assert_called_once_with(
  160. "GET",
  161. "/zone123/dns_records",
  162. type="A",
  163. per_page=10000,
  164. **{"name.exact": "www.example.com", "proxied": "true"}
  165. ) # fmt: skip
  166. self.assertIsNotNone(result)
  167. def test_query_record_with_proxy_false_fallback(self):
  168. """Test _query_record fallback logic when proxied=False filter returns no results"""
  169. provider = CloudflareProvider(self.id, self.token)
  170. with patch("ddns.provider.cloudflare.join_domain") as mock_join, patch.object(
  171. provider, "_request"
  172. ) as mock_request:
  173. mock_join.return_value = "test.example.net"
  174. # First call with extra filter returns empty, second call without filter returns record
  175. mock_request.side_effect = [
  176. [], # No results with proxied=False
  177. [{"id": "rec123", "name": "test.example.net", "type": "A", "content": "1.2.3.4", "proxied": True}],
  178. ]
  179. extra = {"proxied": False}
  180. result = provider._query_record("zone123", "test", "example.net", "A", None, extra)
  181. # Should call twice - first with extra filter, then without
  182. self.assertEqual(mock_request.call_count, 2)
  183. # Note: proxied is converted to lowercase string "false"
  184. mock_request.assert_any_call(
  185. "GET",
  186. "/zone123/dns_records",
  187. type="A",
  188. per_page=10000,
  189. **{"name.exact": "test.example.net", "proxied": "false"}
  190. ) # fmt: skip
  191. mock_request.assert_any_call(
  192. "GET",
  193. "/zone123/dns_records",
  194. type="A",
  195. per_page=10000,
  196. **{"name.exact": "test.example.net"}
  197. ) # fmt: skip
  198. # Should return the record found without extra filter
  199. self.assertIsNotNone(result)
  200. self.assertEqual(result["id"], "rec123")
  201. def test_query_record_with_proxy_true_fallback(self):
  202. """Test _query_record fallback logic when proxied=True filter returns no results"""
  203. provider = CloudflareProvider(self.id, self.token)
  204. with patch("ddns.provider.cloudflare.join_domain") as mock_join, patch.object(
  205. provider, "_request"
  206. ) as mock_request:
  207. mock_join.return_value = "test.example.net"
  208. # First call with extra filter returns empty, second call without filter returns record
  209. mock_request.side_effect = [
  210. [], # No results with proxied=True
  211. [{"id": "rec456", "name": "test.example.net", "type": "A", "content": "1.2.3.4", "proxied": False}],
  212. ]
  213. extra = {"proxied": True}
  214. result = provider._query_record("zone123", "test", "example.net", "A", None, extra)
  215. # Should call twice - first with extra filter, then without
  216. self.assertEqual(mock_request.call_count, 2)
  217. # Should return the record found without extra filter
  218. self.assertIsNotNone(result)
  219. self.assertEqual(result["id"], "rec456")
  220. def test_query_record_with_proxy_found_with_filter(self):
  221. """Test _query_record does not fallback when record is found with extra filter"""
  222. provider = CloudflareProvider(self.id, self.token)
  223. with patch("ddns.provider.cloudflare.join_domain") as mock_join, patch.object(
  224. provider, "_request"
  225. ) as mock_request:
  226. mock_join.return_value = "test.example.net"
  227. # Returns record on first call with extra filter
  228. mock_request.return_value = [
  229. {"id": "rec789", "name": "test.example.net", "type": "A", "content": "1.2.3.4", "proxied": True}
  230. ]
  231. extra = {"proxied": True}
  232. result = provider._query_record("zone123", "test", "example.net", "A", None, extra)
  233. # Should call only once since record found with extra filter
  234. self.assertEqual(mock_request.call_count, 1)
  235. self.assertIsNotNone(result)
  236. self.assertEqual(result["id"], "rec789")
  237. def test_query_record_no_extra_filter(self):
  238. """Test _query_record without extra filters does not perform fallback"""
  239. provider = CloudflareProvider(self.id, self.token)
  240. with patch("ddns.provider.cloudflare.join_domain") as mock_join, patch.object(
  241. provider, "_request"
  242. ) as mock_request:
  243. mock_join.return_value = "www.example.com"
  244. mock_request.return_value = []
  245. # No extra filters
  246. result = provider._query_record("zone123", "www", "example.com", "A", None, {})
  247. # Should call only once since no extra filters
  248. self.assertEqual(mock_request.call_count, 1)
  249. self.assertIsNone(result)
  250. def test_create_record_success(self):
  251. """Test _create_record method with successful creation"""
  252. provider = CloudflareProvider(self.id, self.token)
  253. with patch("ddns.provider.cloudflare.join_domain", autospec=True) as mock_join, patch.object(
  254. provider, "_request"
  255. ) as mock_request:
  256. mock_join.return_value = "www.example.com"
  257. mock_request.return_value = {"id": "rec123", "name": "www.example.com"}
  258. result = provider._create_record("zone123", "www", "example.com", "1.2.3.4", "A", 300, None, {})
  259. mock_join.assert_called_once_with("www", "example.com")
  260. mock_request.assert_called_once_with(
  261. "POST",
  262. "/zone123/dns_records",
  263. name="www.example.com",
  264. type="A",
  265. content="1.2.3.4",
  266. ttl=300,
  267. comment=provider.remark,
  268. )
  269. self.assertTrue(result)
  270. def test_create_record_failure(self):
  271. """Test _create_record method with failed creation"""
  272. provider = CloudflareProvider(self.id, self.token)
  273. with patch("ddns.provider.cloudflare.join_domain") as mock_join, patch.object(
  274. provider, "_request"
  275. ) as mock_request:
  276. mock_join.return_value = "www.example.com"
  277. mock_request.return_value = None # API request failed
  278. result = provider._create_record("zone123", "www", "example.com", "1.2.3.4", "A", None, None, {})
  279. self.assertFalse(result)
  280. def test_create_record_with_extra_params(self):
  281. """Test _create_record method with extra parameters"""
  282. provider = CloudflareProvider(self.id, self.token)
  283. with patch("ddns.provider.cloudflare.join_domain") as mock_join, patch.object(
  284. provider, "_request"
  285. ) as mock_request:
  286. mock_join.return_value = "www.example.com"
  287. mock_request.return_value = {"id": "rec123"}
  288. extra = {"proxied": True, "comment": "Custom comment", "priority": 10}
  289. result = provider._create_record("zone123", "www", "example.com", "1.2.3.4", "A", 300, None, extra)
  290. mock_request.assert_called_once_with(
  291. "POST",
  292. "/zone123/dns_records",
  293. name="www.example.com",
  294. type="A",
  295. content="1.2.3.4",
  296. ttl=300,
  297. proxied=True,
  298. comment="Custom comment",
  299. priority=10,
  300. )
  301. self.assertTrue(result)
  302. def test_update_record_success(self):
  303. """Test _update_record method with successful update"""
  304. provider = CloudflareProvider(self.id, self.token)
  305. old_record = {
  306. "id": "rec123",
  307. "name": "www.example.com",
  308. "comment": "Old comment",
  309. "proxied": False,
  310. "tags": ["tag1"],
  311. "settings": {"ttl": 300},
  312. }
  313. with patch.object(provider, "_request") as mock_request:
  314. mock_request.return_value = {"id": "rec123", "content": "5.6.7.8"}
  315. result = provider._update_record("zone123", old_record, "5.6.7.8", "A", 600, None, {})
  316. mock_request.assert_called_once_with(
  317. "PUT",
  318. "/zone123/dns_records/rec123",
  319. type="A",
  320. name="www.example.com",
  321. content="5.6.7.8",
  322. ttl=600,
  323. comment="Managed by [DDNS](https://ddns.newfuture.cc)", # Default Remark since extra is empty
  324. proxied=False,
  325. tags=["tag1"],
  326. settings={"ttl": 300},
  327. )
  328. self.assertTrue(result)
  329. def test_update_record_failure(self):
  330. """Test _update_record method with failed update"""
  331. provider = CloudflareProvider(self.id, self.token)
  332. old_record = {"id": "rec123", "name": "www.example.com"}
  333. with patch.object(provider, "_request") as mock_request:
  334. mock_request.return_value = None # API request failed
  335. result = provider._update_record("zone123", old_record, "5.6.7.8", "A", None, None, {})
  336. self.assertFalse(result)
  337. def test_update_record_with_extra_params(self):
  338. """Test _update_record method with extra parameters overriding old_record values"""
  339. provider = CloudflareProvider(self.id, self.token)
  340. old_record = {
  341. "id": "rec123",
  342. "name": "www.example.com",
  343. "comment": "Old comment",
  344. "proxied": False,
  345. "tags": ["old_tag"],
  346. "settings": {"old": "setting"},
  347. }
  348. with patch.object(provider, "_request") as mock_request:
  349. mock_request.return_value = {"id": "rec123"}
  350. extra = {"comment": "New comment", "proxied": True, "priority": 20, "tags": ["new_tag"]}
  351. result = provider._update_record("zone123", old_record, "5.6.7.8", "A", 600, None, extra)
  352. mock_request.assert_called_once_with(
  353. "PUT",
  354. "/zone123/dns_records/rec123",
  355. type="A",
  356. name="www.example.com",
  357. content="5.6.7.8",
  358. ttl=600,
  359. comment="New comment", # extra.get("comment", self.remark)
  360. proxied=True, # extra.get("proxied", old_record.get("proxied")) - extra takes priority
  361. priority=20, # From extra
  362. tags=["new_tag"], # extra.get("tags", old_record.get("tags")) - extra takes priority
  363. settings={
  364. "old": "setting"
  365. }, # extra.get("settings", old_record.get("settings")) - falls back to old_record
  366. )
  367. self.assertTrue(result)
  368. def test_update_record_preserves_old_values(self):
  369. """Test _update_record method preserves proxied/tags/settings from old record, uses default comment"""
  370. provider = CloudflareProvider(self.id, self.token)
  371. old_record = {
  372. "id": "rec123",
  373. "name": "www.example.com",
  374. "comment": "Preserve this",
  375. "proxied": True,
  376. "tags": ["important"],
  377. "settings": {"ttl": 300},
  378. }
  379. with patch.object(provider, "_request") as mock_request:
  380. mock_request.return_value = {"id": "rec123"}
  381. # No extra parameters provided
  382. result = provider._update_record("zone123", old_record, "5.6.7.8", "A", 600, None, {})
  383. mock_request.assert_called_once_with(
  384. "PUT",
  385. "/zone123/dns_records/rec123",
  386. type="A",
  387. name="www.example.com",
  388. content="5.6.7.8",
  389. ttl=600,
  390. comment="Managed by [DDNS](https://ddns.newfuture.cc)", # Default Remark
  391. proxied=True, # Preserved from old record
  392. tags=["important"], # Preserved from old record
  393. settings={"ttl": 300}, # Preserved from old record
  394. )
  395. self.assertTrue(result)
  396. class TestCloudflareProviderIntegration(BaseProviderTestCase):
  397. """Integration test cases for CloudflareProvider - testing with minimal mocking"""
  398. def setUp(self):
  399. """Set up test fixtures"""
  400. super(TestCloudflareProviderIntegration, self).setUp()
  401. self.id = "[email protected]"
  402. self.token = "test_api_key"
  403. def test_full_workflow_create_new_record(self):
  404. """Test complete workflow for creating a new record"""
  405. provider = CloudflareProvider(self.id, self.token)
  406. # Mock only the HTTP layer to simulate API responses
  407. with patch.object(provider, "_request") as mock_request:
  408. # Simulate API responses in order: zone query, record query, record creation
  409. mock_request.side_effect = [
  410. [{"id": "zone123", "name": "example.com"}], # _query_zone_id response
  411. [], # _query_record response (no existing record)
  412. {"id": "rec123", "name": "www.example.com"}, # _create_record response
  413. ]
  414. result = provider.set_record("www.example.com", "1.2.3.4", "A", 300)
  415. self.assertTrue(result)
  416. # Verify the actual API calls made
  417. self.assertEqual(mock_request.call_count, 3)
  418. mock_request.assert_any_call("GET", "", **{"name.exact": "example.com", "per_page": 50})
  419. mock_request.assert_any_call(
  420. "GET", "/zone123/dns_records", type="A", per_page=10000, **{"name.exact": "www.example.com"}
  421. )
  422. mock_request.assert_any_call(
  423. "POST",
  424. "/zone123/dns_records",
  425. name="www.example.com",
  426. type="A",
  427. content="1.2.3.4",
  428. ttl=300,
  429. comment="Managed by [DDNS](https://ddns.newfuture.cc)",
  430. )
  431. def test_full_workflow_update_existing_record(self):
  432. """Test complete workflow for updating an existing record"""
  433. provider = CloudflareProvider(self.id, self.token)
  434. with patch.object(provider, "_request") as mock_request:
  435. # Simulate API responses
  436. mock_request.side_effect = [
  437. [{"id": "zone123", "name": "example.com"}], # _query_zone_id response
  438. [ # _query_record response (existing record found)
  439. {"id": "rec123", "name": "www.example.com", "type": "A", "content": "5.6.7.8", "proxied": False}
  440. ],
  441. {"id": "rec123", "content": "1.2.3.4"}, # _update_record response
  442. ]
  443. result = provider.set_record("www.example.com", "1.2.3.4", "A", 300)
  444. self.assertTrue(result)
  445. # Verify the update call was made
  446. mock_request.assert_any_call(
  447. "PUT",
  448. "/zone123/dns_records/rec123",
  449. type="A",
  450. name="www.example.com",
  451. content="1.2.3.4",
  452. ttl=300,
  453. comment="Managed by [DDNS](https://ddns.newfuture.cc)",
  454. proxied=False,
  455. tags=None,
  456. settings=None,
  457. )
  458. def test_full_workflow_zone_not_found(self):
  459. """Test complete workflow when zone is not found"""
  460. provider = CloudflareProvider(self.id, self.token)
  461. with patch.object(provider, "_request") as mock_request:
  462. # Simulate API returning empty array for zone query
  463. mock_request.return_value = []
  464. result = provider.set_record("www.nonexistent.com", "1.2.3.4", "A")
  465. self.assertFalse(result)
  466. def test_full_workflow_create_failure(self):
  467. """Test complete workflow when record creation fails"""
  468. provider = CloudflareProvider(self.id, self.token)
  469. with patch.object(provider, "_request") as mock_request:
  470. # Simulate responses: zone found, no existing record, creation fails
  471. mock_request.side_effect = [
  472. [{"id": "zone123", "name": "example.com"}], # _query_zone_id response
  473. [], # _query_record response (no existing record)
  474. None, # _create_record fails (API returns None)
  475. ]
  476. result = provider.set_record("www.example.com", "1.2.3.4", "A")
  477. self.assertFalse(result)
  478. def test_full_workflow_update_failure(self):
  479. """Test complete workflow when record update fails"""
  480. provider = CloudflareProvider(self.id, self.token)
  481. with patch.object(provider, "_request") as mock_request:
  482. # Simulate responses: zone found, existing record found, update fails
  483. mock_request.side_effect = [
  484. [{"id": "zone123", "name": "example.com"}], # _query_zone_id response
  485. [ # _query_record response (existing record found)
  486. {"id": "rec123", "name": "www.example.com", "type": "A", "content": "5.6.7.8"}
  487. ],
  488. None, # _update_record fails (API returns None)
  489. ]
  490. result = provider.set_record("www.example.com", "1.2.3.4", "A")
  491. self.assertFalse(result)
  492. def test_full_workflow_with_proxy_options(self):
  493. """Test complete workflow with proxy and other Cloudflare-specific options"""
  494. provider = CloudflareProvider(self.id, self.token)
  495. with patch.object(provider, "_request") as mock_request:
  496. # Simulate successful creation with custom options
  497. mock_request.side_effect = [
  498. [{"id": "zone123", "name": "example.com"}], # _query_zone_id response
  499. [], # _query_record response with extra filter (no existing record)
  500. [], # _query_record fallback without extra filter (no existing record)
  501. {"id": "rec123", "name": "www.example.com"}, # _create_record response
  502. ]
  503. result = provider.set_record("www.example.com", "1.2.3.4", "A", 300, None, proxied=True, priority=10)
  504. self.assertTrue(result)
  505. # Verify that extra parameters are passed through correctly
  506. mock_request.assert_any_call(
  507. "POST",
  508. "/zone123/dns_records",
  509. name="www.example.com",
  510. type="A",
  511. content="1.2.3.4",
  512. ttl=300,
  513. comment="Managed by [DDNS](https://ddns.newfuture.cc)",
  514. proxied=True,
  515. priority=10,
  516. )
  517. def test_full_workflow_bearer_token_auth(self):
  518. """Test complete workflow using Bearer token authentication"""
  519. provider = CloudflareProvider("", self.token) # No email, Bearer token only
  520. with patch.object(provider, "_request") as mock_request:
  521. # Simulate successful workflow
  522. mock_request.side_effect = [
  523. [{"id": "zone123", "name": "example.com"}], # _query_zone_id response
  524. [], # _query_record response (no existing record)
  525. {"id": "rec123", "name": "www.example.com"}, # _create_record response
  526. ]
  527. result = provider.set_record("www.example.com", "1.2.3.4", "A")
  528. self.assertTrue(result)
  529. # The workflow should work the same regardless of auth method
  530. if __name__ == "__main__":
  531. unittest.main()