Skip to content

Commit ca0645f

Browse files
authored
#4681 Enable patch request on Cloudflare zone DNS settings (#4748)
1 parent 3e8eeea commit ca0645f

6 files changed

Lines changed: 173 additions & 32 deletions

File tree

src/registrar/services/cloudflare_service.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,43 @@ def create_cf_zone(self, zone_name: str, x_account_id: str):
116116
raise
117117
return resp.json()
118118

119+
def update_zone_dns_settings(
120+
self, zone_id: str, *, zone_mode: str = "dns_only", nameservers_type: str = "custom.tenant", ns_set: int = 1
121+
) -> CloudflareDnsSettingsUpdateResponse:
122+
"""PATCH /zones/{zone_id}/dns_settings
123+
Required settings:
124+
- zone_mode: "standard" | "cdn_only" | "dns_only"
125+
- nameservers_type: "cloudflare.standard"
126+
"cloudflare.standard.random"
127+
"custom.account"
128+
"custom.tenant"
129+
- ns_set: Min 1, max 5. Default 1 when not passed as argument.
130+
"""
131+
appended_url = f"/zones/{zone_id}/dns_settings"
132+
data = {
133+
"zone_mode": zone_mode,
134+
"nameservers": {"ns_set": ns_set, "type": nameservers_type},
135+
}
136+
137+
try:
138+
resp = self.client.patch(appended_url, json=data)
139+
resp.raise_for_status()
140+
logger.info(
141+
"Updated zone DNS settings for zone_id=%s (zone_mode=%s, nameservers.type=%s, namservers.ns_set=%s)",
142+
zone_id,
143+
zone_mode,
144+
nameservers_type,
145+
ns_set,
146+
)
147+
except RequestError as e:
148+
logger.error(f"Failed to update dns settings for zone {zone_id}: {e}")
149+
raise
150+
except HTTPStatusError as e:
151+
logger.error(f"Error {e.response.status_code} while updating dns settings: {e}")
152+
raise
153+
154+
return CloudflareDnsSettingsUpdateResponse.from_json(resp.json())
155+
119156
def create_dns_record(self, zone_id: str, record_data: dict[str, Any]):
120157
appended_url = f"/zones/{zone_id}/dns_records"
121158
try:

src/registrar/services/dns_host_service.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ def update_account_dns_settings(self, x_account_id: str) -> CloudflareDnsSetting
3434
"""Ensure required Cloudflare DNS settings are applied for an account."""
3535
return self.dns_vendor_service.update_account_dns_settings(x_account_id)
3636

37+
def update_zone_dns_settings(self, x_zone_id: str) -> CloudflareDnsSettingsUpdateResponse:
38+
"""Ensure required Cloudflare DNS settings are applied for a zone."""
39+
return self.dns_vendor_service.update_zone_dns_settings(x_zone_id)
40+
3741
def _find_account_tag_by_pubname(self, items, name):
3842
"""Find an item by name in a list of dictionaries."""
3943
return next((item.get("account_tag") for item in items if item.get("account_pubname") == name), None)

src/registrar/services/mock_cloudflare_service.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,25 @@ def _register_zone_mocks(self):
115115
side_effect=self._mock_update_dns_record_response
116116
)
117117

118+
# PATCH account dns_settings
119+
self._mock_context.patch(url__regex=r"/zones/[\w-]+/dns_settings").mock(
120+
side_effect=self._mock_update_zone_dns_settings_response
121+
)
122+
123+
def _mock_update_zone_dns_settings_response(self, request: httpx.Request) -> httpx.Response:
124+
return httpx.Response(
125+
200,
126+
json={
127+
"result": {
128+
"zone_mode": "dns_only",
129+
"nameservers": {"ns_set": 2, "type": "custom.tenant"},
130+
},
131+
"success": True,
132+
"errors": [],
133+
"messages": [],
134+
},
135+
)
136+
118137
def _mock_get_page_accounts_response(self, request) -> httpx.Response:
119138
logger.debug("😎 Mocking accounts GET")
120139
# use exists.gov domain to simulate an account that already exists

src/registrar/tests/services/test_cloudflare_service.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,3 +423,41 @@ def test_update_account_dns_settings_failure(self):
423423
self.service.update_account_dns_settings(account_id)
424424

425425
self.assertIn(error["message"], str(context.exception))
426+
427+
def test_update_zone_dns_settings_success(self):
428+
"""Test successful update_zone_dns_settings call"""
429+
zone_id = "54321"
430+
return_value = {
431+
"success": True,
432+
"result": {
433+
"zone_mode": "dns_only",
434+
"nameservers": {"ns_set": 2, "type": "custom.tenant"},
435+
},
436+
"errors": [],
437+
"messages": [],
438+
}
439+
mock_response = self._setUpSuccessMockResponse(return_value)
440+
self.service.client.patch.return_value = mock_response
441+
442+
resp = self.service.update_zone_dns_settings(zone_id)
443+
444+
self.assertTrue(resp.success)
445+
self.assertEqual(resp.result["zone_mode"], "dns_only")
446+
self.assertEqual(resp.result["nameservers"]["ns_set"], 2)
447+
self.assertEqual(resp.result["nameservers"]["type"], "custom.tenant")
448+
self.assertEqual(resp.errors, [])
449+
450+
def test_update_zone_dns_settings_failure(self):
451+
"""Test update_zone_dns_settings when error results during call"""
452+
zone_id = "54321"
453+
454+
for case in self.failure_cases:
455+
with self.subTest(msg=case["test_name"], **case):
456+
error = case["error"]
457+
mock_response = self._setUpFailureMockResponse(error)
458+
self.service.client.patch.return_value = mock_response
459+
460+
with self.assertRaises(error["exception"]) as context:
461+
self.service.update_zone_dns_settings(zone_id)
462+
463+
self.assertIn(error["message"], str(context.exception))

src/registrar/tests/services/test_dns_host_service.py

Lines changed: 63 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,69 @@ def test_create_cf_record_failure(self, mock_create_dns_record):
263263
self.service.create_and_save_record(zone_id, record_data)
264264
self.assertIn("Bad request: missing name", str(context.exception))
265265

266+
def test_update_account_dns_settings_success(self):
267+
x_account_id = "12345"
268+
expected_response = CloudflareDnsSettingsUpdateResponse(
269+
success=True,
270+
result={
271+
"zone_defaults": {
272+
"zone_mode": "dns_only",
273+
"nameservers": {"type": "custom.tenant"},
274+
}
275+
},
276+
errors=[],
277+
messages=[],
278+
)
279+
self.service.dns_vendor_service.update_account_dns_settings = Mock(return_value=expected_response)
280+
281+
response = self.service.update_account_dns_settings(x_account_id)
282+
283+
self.service.dns_vendor_service.update_account_dns_settings.assert_called_once_with(x_account_id)
284+
self.assertTrue(response.success)
285+
self.assertEqual(response.result["zone_defaults"]["zone_mode"], "dns_only")
286+
self.assertEqual(response.result["zone_defaults"]["nameservers"]["type"], "custom.tenant")
287+
self.assertEqual(response.errors, [])
288+
289+
def test_update_account_dns_settings_failure(self):
290+
x_account_id = "12345"
291+
self.service.dns_vendor_service.update_account_dns_settings = Mock(
292+
side_effect=HTTPStatusError(message="Error updating DNS settings", request=Mock(), response=Mock())
293+
)
294+
295+
with self.assertRaises(HTTPStatusError):
296+
self.service.update_account_dns_settings(x_account_id)
297+
298+
def test_update_zone_dns_settings_success(self):
299+
x_zone_id = "54321"
300+
expected_response = CloudflareDnsSettingsUpdateResponse(
301+
success=True,
302+
result={
303+
"zone_mode": "dns_only",
304+
"nameservers": {"ns_set": 2, "type": "custom.tenant"},
305+
},
306+
errors=[],
307+
messages=[],
308+
)
309+
self.service.dns_vendor_service.update_zone_dns_settings = Mock(return_value=expected_response)
310+
311+
response = self.service.update_zone_dns_settings(x_zone_id)
312+
313+
self.service.dns_vendor_service.update_zone_dns_settings.assert_called_once_with(x_zone_id)
314+
self.assertTrue(response.success)
315+
self.assertEqual(response.result["zone_mode"], "dns_only")
316+
self.assertEqual(response.result["nameservers"]["ns_set"], 2)
317+
self.assertEqual(response.result["nameservers"]["type"], "custom.tenant")
318+
self.assertEqual(response.errors, [])
319+
320+
def test_update_zone_dns_settings_failure(self):
321+
x_zone_id = "54321"
322+
self.service.dns_vendor_service.update_zone_dns_settings = Mock(
323+
side_effect=HTTPStatusError(message="Error updating DNS settings", request=Mock(), response=Mock())
324+
)
325+
326+
with self.assertRaises(HTTPStatusError):
327+
self.service.update_zone_dns_settings(x_zone_id)
328+
266329

267330
class TestDnsHostServiceDB(TestCase):
268331
def setUp(self):
@@ -747,35 +810,3 @@ def test_update_db_record_with_bad_data_fails(self):
747810
self.assertEqual(dns_record.content, self.vendor_record_data["result"].get("content"))
748811
self.assertEqual(dns_record.ttl, self.vendor_record_data["result"].get("ttl"))
749812
self.assertEqual(dns_record.comment, self.vendor_record_data["result"].get("comment"))
750-
751-
def test_update_account_dns_settings_success(self):
752-
x_account_id = "12345"
753-
expected_response = CloudflareDnsSettingsUpdateResponse(
754-
success=True,
755-
result={
756-
"zone_defaults": {
757-
"zone_mode": "dns_only",
758-
"nameservers": {"type": "custom.tenant"},
759-
}
760-
},
761-
errors=[],
762-
messages=[],
763-
)
764-
self.service.dns_vendor_service.update_account_dns_settings = Mock(return_value=expected_response)
765-
766-
response = self.service.update_account_dns_settings(x_account_id)
767-
768-
self.service.dns_vendor_service.update_account_dns_settings.assert_called_once_with(x_account_id)
769-
self.assertTrue(response.success)
770-
self.assertEqual(response.result["zone_defaults"]["zone_mode"], "dns_only")
771-
self.assertEqual(response.result["zone_defaults"]["nameservers"]["type"], "custom.tenant")
772-
self.assertEqual(response.errors, [])
773-
774-
def test_update_account_dns_settings_failure(self):
775-
x_account_id = "12345"
776-
self.service.dns_vendor_service.update_account_dns_settings = Mock(
777-
side_effect=HTTPStatusError(message="Error updating DNS settings", request=Mock(), response=Mock())
778-
)
779-
780-
with self.assertRaises(HTTPStatusError):
781-
self.service.update_account_dns_settings(x_account_id)

src/registrar/tests/services/test_mock_cloudflare_service.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,15 @@ def test_mock_update_account_dns_settings_response(self):
189189
self.assertEqual(resp.result["zone_defaults"]["nameservers"]["type"], "custom.tenant")
190190
self.assertEqual(resp.errors, [])
191191
self.assertEqual(resp.messages, [])
192+
193+
def test_mock_update_zone_dns_settings_response(self):
194+
zone_id = self.mock_api_service.fake_zone_id
195+
196+
resp = self.service.update_zone_dns_settings(zone_id)
197+
198+
self.assertTrue(resp.success)
199+
self.assertEqual(resp.result["zone_mode"], "dns_only")
200+
self.assertEqual(resp.result["nameservers"]["ns_set"], 2)
201+
self.assertEqual(resp.result["nameservers"]["type"], "custom.tenant")
202+
self.assertEqual(resp.errors, [])
203+
self.assertEqual(resp.messages, [])

0 commit comments

Comments
 (0)