diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1eae2d4..a1dc57d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -63,7 +63,7 @@ default-deploy:
tags:
- docker
- lnx
- - 64bit
+ - server
rules:
- if: $CI_COMMIT_TAG == null
script:
@@ -122,7 +122,7 @@ core-deploy:
tags:
- docker
- lnx
- - 64bit
+ - server
rules:
- if: $CI_COMMIT_TAG =~ /^v[0-9.]+/
script:
@@ -182,7 +182,7 @@ extensions-deploy:
tags:
- docker
- lnx
- - 64bit
+ - server
rules:
- if: $CI_COMMIT_TAG =~ /^[a-z]+\/v[0-9.]+/
script:
diff --git a/src/Extensions/Cloudflare.Dns/DnsDnssecExtensions.cs b/src/Extensions/Cloudflare.Dns/DnsDnssecExtensions.cs
new file mode 100644
index 0000000..2a74f3e
--- /dev/null
+++ b/src/Extensions/Cloudflare.Dns/DnsDnssecExtensions.cs
@@ -0,0 +1,59 @@
+using System.Threading;
+using System.Threading.Tasks;
+using AMWD.Net.Api.Cloudflare.Dns.Internals;
+
+namespace AMWD.Net.Api.Cloudflare.Dns
+{
+ ///
+ /// Extensions for DNS DNSSEC records.
+ ///
+ public static class DnsDnssecExtensions
+ {
+ ///
+ /// Delete DNSSEC.
+ ///
+ /// The instance.
+ /// The zone identifier.
+ /// A cancellation token used to propagate notification that this operation should be canceled.
+ public static Task> DeleteDnssecRecords(this ICloudflareClient client, string zoneId, CancellationToken cancellationToken = default)
+ {
+ zoneId.ValidateCloudflareId();
+
+ return client.DeleteAsync($"/zones/{zoneId}/dnssec", null, cancellationToken);
+ }
+
+ ///
+ /// Enable or disable DNSSEC.
+ ///
+ /// The instance.
+ /// The request.
+ /// A cancellation token used to propagate notification that this operation should be canceled.
+ public static Task> EditDnssecStatus(this ICloudflareClient client, EditDnssecStatusRequest request, CancellationToken cancellationToken = default)
+ {
+ request.ZoneId.ValidateCloudflareId();
+
+ var req = new InternalEditDnssecStatusRequest
+ {
+ DnssecMultiSigner = request.DnssecMultiSigner,
+ DnssecPresigned = request.DnssecPresigned,
+ DnssecUseNsec3 = request.DnssecUseNsec3,
+ Status = request.Status
+ };
+
+ return client.PatchAsync($"/zones/{request.ZoneId}/dnssec", req, cancellationToken);
+ }
+
+ ///
+ /// Details about DNSSEC status and configuration.
+ ///
+ /// The instance.
+ /// The zone identifier.
+ /// A cancellation token used to propagate notification that this operation should be canceled.
+ public static Task> DnssecDetails(this ICloudflareClient client, string zoneId, CancellationToken cancellationToken = default)
+ {
+ zoneId.ValidateCloudflareId();
+
+ return client.GetAsync($"/zones/{zoneId}/dnssec", null, cancellationToken);
+ }
+ }
+}
diff --git a/src/Extensions/Cloudflare.Dns/Internals/InternalEditDnssecStatusRequest.cs b/src/Extensions/Cloudflare.Dns/Internals/InternalEditDnssecStatusRequest.cs
new file mode 100644
index 0000000..74739d7
--- /dev/null
+++ b/src/Extensions/Cloudflare.Dns/Internals/InternalEditDnssecStatusRequest.cs
@@ -0,0 +1,33 @@
+namespace AMWD.Net.Api.Cloudflare.Dns.Internals
+{
+ internal class InternalEditDnssecStatusRequest
+ {
+ [JsonProperty("dnssec_multi_signer")]
+ public bool? DnssecMultiSigner { get; set; }
+
+ ///
+ /// If , allows Cloudflare to transfer in a DNSSEC-signed zone including signatures from an external provider, without requiring Cloudflare to sign any records on the fly.
+ ///
+ ///
+ /// Note that this feature has some limitations. See Cloudflare as Secondary for details.
+ ///
+ [JsonProperty("dnssec_presigned")]
+ public bool? DnssecPresigned { get; set; }
+
+ ///
+ /// If , enables the use of NSEC3 together with DNSSEC on the zone.
+ ///
+ ///
+ /// Combined with setting to , this enables the use of NSEC3 records when transferring in from an external provider.
+ /// If is instead set to (default), NSEC3 records will be generated and signed at request time.
+ ///
+ [JsonProperty("dnssec_use_nsec3")]
+ public bool? DnssecUseNsec3 { get; set; }
+
+ ///
+ /// Status of DNSSEC, based on user-desired state and presence of necessary records.
+ ///
+ [JsonProperty("status")]
+ public DnssecEditStatus? Status { get; set; }
+ }
+}
diff --git a/src/Extensions/Cloudflare.Dns/Models/DNSSEC.cs b/src/Extensions/Cloudflare.Dns/Models/DNSSEC.cs
new file mode 100644
index 0000000..b5522d7
--- /dev/null
+++ b/src/Extensions/Cloudflare.Dns/Models/DNSSEC.cs
@@ -0,0 +1,148 @@
+using System.Runtime.Serialization;
+using Newtonsoft.Json.Converters;
+
+namespace AMWD.Net.Api.Cloudflare.Dns
+{
+ ///
+ /// Represents DNS Security Extensions (DNSSEC) information from Cloudflare.
+ ///
+ public class DNSSEC
+ {
+ ///
+ /// Algorithm key code.
+ ///
+ [JsonProperty("algorithm")]
+ public string? Algorithm { get; set; }
+
+ ///
+ /// Digest hash.
+ ///
+ [JsonProperty("digest")]
+ public string? Digest { get; set; }
+
+ ///
+ /// Type of digest algorithm.
+ ///
+ [JsonProperty("digest_algorithm")]
+ public string? DigestAlgorithm { get; set; }
+
+ ///
+ /// Coded type for digest algorithm.
+ ///
+ [JsonProperty("digest_type")]
+ public string? DigestType { get; set; }
+
+ ///
+ /// If , multi-signer DNSSEC is enabled on the zone, allowing multiple providers to serve a DNSSEC-signed zone at the same time.
+ ///
+ ///
+ /// This is required for DNSKEY records (except those automatically generated by Cloudflare) to be added to the zone.
+ ///
+ /// See Multi-signer DNSSEC for details.
+ ///
+ [JsonProperty("dnssec_multi_signer")]
+ public bool? DnssecMultiSigner { get; set; }
+
+ ///
+ /// If , allows Cloudflare to transfer in a DNSSEC-signed zone including signatures from an external provider, without requiring Cloudflare to sign any records on the fly.
+ ///
+ ///
+ /// Note that this feature has some limitations.
+ /// See Cloudflare as Secondary for details.
+ ///
+ [JsonProperty("dnssec_presigned")]
+ public bool? DnssecPresigned { get; set; }
+
+ ///
+ /// If , enables the use of NSEC3 together with DNSSEC on the zone.
+ ///
+ ///
+ /// Combined with setting to , this enables the use of NSEC3 records when transferring in from an external provider.
+ /// If is instead set to (default), NSEC3 records will be generated and signed at request time.
+ ///
+ /// See DNSSEC with NSEC3 for details.
+ ///
+ [JsonProperty("dnssec_use_nsec3")]
+ public bool? DnssecUseNsec3 { get; set; }
+
+ ///
+ /// Full DS record.
+ ///
+ [JsonProperty("ds")]
+ public string? Ds { get; set; }
+
+ ///
+ /// Flag for DNSSEC record.
+ ///
+ [JsonProperty("flags")]
+ public int? Flags { get; set; }
+
+ ///
+ /// Code for key tag.
+ ///
+ [JsonProperty("key_tag")]
+ public int? KeyTag { get; set; }
+
+ ///
+ /// Algorithm key type.
+ ///
+ [JsonProperty("key_type")]
+ public string? KeyType { get; set; }
+
+ ///
+ /// When DNSSEC was last modified.
+ ///
+ [JsonProperty("modified_on")]
+ public DateTime? ModifiedOn { get; set; }
+
+ ///
+ /// Public key for DS record.
+ ///
+ [JsonProperty("public_key")]
+ public string? PublicKey { get; set; }
+
+ ///
+ /// Status of DNSSEC, based on user-desired state and presence of necessary records.
+ ///
+ [JsonProperty("status")]
+ public DNSSECStatus? Status { get; set; }
+ }
+
+ ///
+ /// Status of DNSSEC, based on user-desired state and presence of necessary records.
+ /// Source
+ ///
+ [JsonConverter(typeof(StringEnumConverter))]
+ public enum DNSSECStatus
+ {
+ ///
+ /// Active.
+ ///
+ [EnumMember(Value = "active")]
+ Active = 1,
+
+ ///
+ /// Pending.
+ ///
+ [EnumMember(Value = "pending")]
+ Pending = 2,
+
+ ///
+ /// Disabled.
+ ///
+ [EnumMember(Value = "disabled")]
+ Disabled = 3,
+
+ ///
+ /// Pending disabled.
+ ///
+ [EnumMember(Value = "pending-disabled")]
+ PendingDisabled = 4,
+
+ ///
+ /// Error.
+ ///
+ [EnumMember(Value = "error")]
+ Error = 5
+ }
+}
diff --git a/src/Extensions/Cloudflare.Dns/README.md b/src/Extensions/Cloudflare.Dns/README.md
index d9ccadc..8502d0a 100644
--- a/src/Extensions/Cloudflare.Dns/README.md
+++ b/src/Extensions/Cloudflare.Dns/README.md
@@ -13,6 +13,13 @@ This package contains the feature set of the _DNS_ section of the Cloudflare API
### [DNS]
+### [DNSSEC]
+
+- [Delete DNSSEC Records](https://developers.cloudflare.com/api/resources/dns/subresources/dnssec/methods/delete/)
+- [Edit DNSSEC Status](https://developers.cloudflare.com/api/resources/dns/subresources/dnssec/methods/edit/)
+- [DNSSEC Details](https://developers.cloudflare.com/api/resources/dns/subresources/dnssec/methods/get/)
+
+
#### [Records]
- [Batch DNS Records](https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/batch/)
@@ -46,6 +53,7 @@ This package contains the feature set of the _DNS_ section of the Cloudflare API
- [Show DNS Settings](https://developers.cloudflare.com/api/resources/dns/subresources/settings/subresources/zone/methods/get/)
+
---
Published under MIT License (see [choose a license])
@@ -57,8 +65,8 @@ Published under MIT License (see [choose a license])
[Account Custom Nameservers]: https://developers.cloudflare.com/api/resources/custom_nameservers/
[DNS]: https://developers.cloudflare.com/api/resources/dns/
-[Records]: https://developers.cloudflare.com/api/resources/dns/subresources/records/
-
-[Settings]: https://developers.cloudflare.com/api/resources/dns/subresources/settings/
-[Account]: https://developers.cloudflare.com/api/resources/dns/subresources/settings/subresources/account/
-[Zone]: https://developers.cloudflare.com/api/resources/dns/subresources/settings/subresources/zone/
+ [DNSSEC]: https://developers.cloudflare.com/api/resources/dns/subresources/dnssec/
+ [Records]: https://developers.cloudflare.com/api/resources/dns/subresources/records/
+ [Settings]: https://developers.cloudflare.com/api/resources/dns/subresources/settings/
+ [Account]: https://developers.cloudflare.com/api/resources/dns/subresources/settings/subresources/account/
+ [Zone]: https://developers.cloudflare.com/api/resources/dns/subresources/settings/subresources/zone/
diff --git a/src/Extensions/Cloudflare.Dns/Requests/EditDnssecStatusRequest.cs b/src/Extensions/Cloudflare.Dns/Requests/EditDnssecStatusRequest.cs
new file mode 100644
index 0000000..67e97fa
--- /dev/null
+++ b/src/Extensions/Cloudflare.Dns/Requests/EditDnssecStatusRequest.cs
@@ -0,0 +1,76 @@
+using System.Runtime.Serialization;
+using Newtonsoft.Json.Converters;
+
+namespace AMWD.Net.Api.Cloudflare.Dns
+{
+ ///
+ /// Represents a request to edit the DNSSEC (Domain Name System Security Extensions) status for a domain.
+ ///
+ public class EditDnssecStatusRequest
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The zone identifier.
+ public EditDnssecStatusRequest(string zoneId)
+ {
+ ZoneId = zoneId;
+ }
+
+ ///
+ /// The zone identifier.
+ ///
+ public string ZoneId { get; set; }
+
+ ///
+ /// If , multi-signer DNSSEC is enabled on the zone, allowing multiple providers to serve a DNSSEC-signed zone at the same time.
+ ///
+ ///
+ /// This is required for DNSKEY records (except those automatically generated by Cloudflare) to be added to the zone.
+ ///
+ /// See Multi-signer DNSSEC for details.
+ ///
+ public bool? DnssecMultiSigner { get; set; }
+
+ ///
+ /// If , allows Cloudflare to transfer in a DNSSEC-signed zone including signatures from an external provider, without requiring Cloudflare to sign any records on the fly.
+ ///
+ ///
+ /// Note that this feature has some limitations. See Cloudflare as Secondary for details.
+ ///
+ public bool? DnssecPresigned { get; set; }
+
+ ///
+ /// If , enables the use of NSEC3 together with DNSSEC on the zone.
+ ///
+ ///
+ /// Combined with setting to , this enables the use of NSEC3 records when transferring in from an external provider.
+ /// If is instead set to (default), NSEC3 records will be generated and signed at request time.
+ ///
+ public bool? DnssecUseNsec3 { get; set; }
+
+ ///
+ /// Status of DNSSEC, based on user-desired state and presence of necessary records.
+ ///
+ public DnssecEditStatus? Status { get; set; }
+ }
+
+ ///
+ /// Status of DNSSEC, based on user-desired state and presence of necessary records.
+ ///
+ [JsonConverter(typeof(StringEnumConverter))]
+ public enum DnssecEditStatus
+ {
+ ///
+ /// DNSSEC is enabled.
+ ///
+ [EnumMember(Value = "active")]
+ Active = 1,
+
+ ///
+ /// DNSSEC is disabled.
+ ///
+ [EnumMember(Value = "disabled")]
+ Disabled = 3,
+ }
+}
diff --git a/test/Directory.Build.props b/test/Directory.Build.props
index ab3c9c4..83860e1 100644
--- a/test/Directory.Build.props
+++ b/test/Directory.Build.props
@@ -13,8 +13,8 @@
-
-
+
+
diff --git a/test/Extensions/Cloudflare.Dns.Tests/DnsDnssecExtensions/DeleteDnssecRecordsTest.cs b/test/Extensions/Cloudflare.Dns.Tests/DnsDnssecExtensions/DeleteDnssecRecordsTest.cs
new file mode 100644
index 0000000..39893eb
--- /dev/null
+++ b/test/Extensions/Cloudflare.Dns.Tests/DnsDnssecExtensions/DeleteDnssecRecordsTest.cs
@@ -0,0 +1,82 @@
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using AMWD.Net.Api.Cloudflare;
+using AMWD.Net.Api.Cloudflare.Dns;
+using Moq;
+
+namespace Cloudflare.Dns.Tests.DnsDnssecExtensions
+{
+ [TestClass]
+ public class DeleteDnssecRecordsTest
+ {
+ private const string ZoneId = "023e105f4ecef8ad9ca31a8372d0c353";
+
+ private Mock _clientMock;
+
+ private CloudflareResponse _response;
+
+ private List<(string RequestPath, IQueryParameterFilter? QueryFilter)> _callbacks;
+
+ [TestInitialize]
+ public void Initialize()
+ {
+ _callbacks = [];
+
+ _response = new CloudflareResponse
+ {
+ Success = true,
+ Messages =
+ [
+ new ResponseInfo(1000, "Message 1")
+ ],
+ Errors =
+ [
+ new ResponseInfo(1000, "Error 1")
+ ],
+ Result = "023e105f4ecef8ad9ca31a8372d0c353"
+ };
+ }
+
+ [TestMethod]
+ public async Task ShouldDeleteDnssecRecords()
+ {
+ // Arrange
+ var client = GetClient();
+
+ // Act
+ var response = await client.DeleteDnssecRecords(ZoneId);
+
+ // Assert
+ Assert.IsNotNull(response);
+ Assert.IsTrue(response.Success);
+ Assert.AreEqual(_response.Result, response.Result);
+
+ Assert.AreEqual(1, _callbacks.Count);
+
+ var callback = _callbacks.First();
+ Assert.AreEqual($"/zones/{ZoneId}/dnssec", callback.RequestPath);
+ Assert.IsNull(callback.QueryFilter);
+
+ _clientMock.Verify(m => m.DeleteAsync(
+ $"/zones/{ZoneId}/dnssec",
+ null,
+ It.IsAny()), Times.Once);
+ _clientMock.VerifyNoOtherCalls();
+ }
+
+ private ICloudflareClient GetClient()
+ {
+ _clientMock = new Mock();
+ _clientMock
+ .Setup(m => m.DeleteAsync(
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny()))
+ .Callback((requestPath, queryFilter, _) => _callbacks.Add((requestPath, queryFilter)))
+ .ReturnsAsync(() => _response);
+
+ return _clientMock.Object;
+ }
+ }
+}
diff --git a/test/Extensions/Cloudflare.Dns.Tests/DnsDnssecExtensions/DnssecDetailsTest.cs b/test/Extensions/Cloudflare.Dns.Tests/DnsDnssecExtensions/DnssecDetailsTest.cs
new file mode 100644
index 0000000..2518297
--- /dev/null
+++ b/test/Extensions/Cloudflare.Dns.Tests/DnsDnssecExtensions/DnssecDetailsTest.cs
@@ -0,0 +1,98 @@
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using AMWD.Net.Api.Cloudflare;
+using AMWD.Net.Api.Cloudflare.Dns;
+using Moq;
+
+namespace Cloudflare.Dns.Tests.DnsDnssecExtensions
+{
+ [TestClass]
+ public class DnssecDetailsTest
+ {
+ private const string ZoneId = "023e105f4ecef8ad9ca31a8372d0c353";
+
+ private Mock _clientMock;
+
+ private CloudflareResponse _response;
+
+ private List<(string RequestPath, IQueryParameterFilter QueryFilter)> _callbacks;
+
+ [TestInitialize]
+ public void Initialize()
+ {
+ _callbacks = [];
+
+ _response = new CloudflareResponse
+ {
+ Success = true,
+ Messages =
+ [
+ new ResponseInfo(1000, "Message 1")
+ ],
+ Errors =
+ [
+ new ResponseInfo(1000, "Error 1")
+ ],
+ Result = new DNSSEC
+ {
+ Algorithm = "ECDSAP256SHA256",
+ Digest = "1234567890ABCDEF",
+ DigestAlgorithm = "SHA256",
+ DigestType = "2",
+ DnssecMultiSigner = true,
+ DnssecPresigned = false,
+ DnssecUseNsec3 = true,
+ Ds = "12345 13 2 1234567890ABCDEF",
+ Flags = 257,
+ KeyTag = 12345,
+ KeyType = "ECDSAP256SHA256",
+ ModifiedOn = DateTime.Parse("2025-08-02 10:20:30"),
+ PublicKey = "ABCDEF1234567890",
+ Status = DNSSECStatus.Active
+ }
+ };
+ }
+
+ [TestMethod]
+ public async Task ShouldGetDnssecDetails()
+ {
+ // Arrange
+ var client = GetClient();
+
+ // Act
+ var response = await client.DnssecDetails(ZoneId);
+
+ // Assert
+ Assert.IsNotNull(response);
+ Assert.IsTrue(response.Success);
+ Assert.AreEqual(_response.Result, response.Result);
+
+ Assert.AreEqual(1, _callbacks.Count);
+
+ var callback = _callbacks.First();
+ Assert.AreEqual($"/zones/{ZoneId}/dnssec", callback.RequestPath);
+ Assert.IsNull(callback.QueryFilter);
+
+ _clientMock.Verify(m => m.GetAsync(
+ $"/zones/{ZoneId}/dnssec",
+ null,
+ It.IsAny()), Times.Once);
+ _clientMock.VerifyNoOtherCalls();
+ }
+
+ private ICloudflareClient GetClient()
+ {
+ _clientMock = new Mock();
+ _clientMock
+ .Setup(m => m.GetAsync(
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny()))
+ .Callback((requestPath, queryFilter, _) => _callbacks.Add((requestPath, queryFilter)))
+ .ReturnsAsync(() => _response);
+
+ return _clientMock.Object;
+ }
+ }
+}
diff --git a/test/Extensions/Cloudflare.Dns.Tests/DnsDnssecExtensions/EditDnssecStatusTest.cs b/test/Extensions/Cloudflare.Dns.Tests/DnsDnssecExtensions/EditDnssecStatusTest.cs
new file mode 100644
index 0000000..f38a753
--- /dev/null
+++ b/test/Extensions/Cloudflare.Dns.Tests/DnsDnssecExtensions/EditDnssecStatusTest.cs
@@ -0,0 +1,114 @@
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using AMWD.Net.Api.Cloudflare;
+using AMWD.Net.Api.Cloudflare.Dns;
+using AMWD.Net.Api.Cloudflare.Dns.Internals;
+using Moq;
+
+namespace Cloudflare.Dns.Tests.DnsDnssecExtensions
+{
+ [TestClass]
+ public class EditDnssecStatusTest
+ {
+ private const string ZoneId = "023e105f4ecef8ad9ca31a8372d0c353";
+
+ private Mock _clientMock;
+
+ private CloudflareResponse _response;
+
+ private List<(string RequestPath, InternalEditDnssecStatusRequest Request)> _callbacks;
+
+ private EditDnssecStatusRequest _request;
+
+ [TestInitialize]
+ public void Initialize()
+ {
+ _callbacks = [];
+
+ _response = new CloudflareResponse
+ {
+ Success = true,
+ Messages =
+ [
+ new ResponseInfo(1000, "Message 1")
+ ],
+ Errors =
+ [
+ new ResponseInfo(1000, "Error 1")
+ ],
+ Result = new DNSSEC
+ {
+ Algorithm = "ECDSAP256SHA256",
+ Digest = "1234567890ABCDEF",
+ DigestAlgorithm = "SHA256",
+ DigestType = "2",
+ DnssecMultiSigner = true,
+ DnssecPresigned = false,
+ DnssecUseNsec3 = true,
+ Ds = "12345 13 2 1234567890ABCDEF",
+ Flags = 257,
+ KeyTag = 12345,
+ KeyType = "ECDSAP256SHA256",
+ ModifiedOn = DateTime.UtcNow,
+ PublicKey = "ABCDEF1234567890",
+ Status = DNSSECStatus.Active
+ }
+ };
+
+ _request = new EditDnssecStatusRequest(ZoneId)
+ {
+ DnssecMultiSigner = true,
+ DnssecPresigned = false,
+ DnssecUseNsec3 = true,
+ Status = DnssecEditStatus.Active
+ };
+ }
+
+ [TestMethod]
+ public async Task ShouldEditDnssecStatus()
+ {
+ // Arrange
+ var client = GetClient();
+
+ // Act
+ var response = await client.EditDnssecStatus(_request);
+
+ // Assert
+ Assert.IsNotNull(response);
+ Assert.IsTrue(response.Success);
+ Assert.AreEqual(_response.Result, response.Result);
+
+ Assert.AreEqual(1, _callbacks.Count);
+
+ var callback = _callbacks.First();
+ Assert.AreEqual($"/zones/{ZoneId}/dnssec", callback.RequestPath);
+
+ Assert.IsNotNull(callback.Request);
+ Assert.AreEqual(_request.DnssecMultiSigner, callback.Request.DnssecMultiSigner);
+ Assert.AreEqual(_request.DnssecPresigned, callback.Request.DnssecPresigned);
+ Assert.AreEqual(_request.DnssecUseNsec3, callback.Request.DnssecUseNsec3);
+ Assert.AreEqual(_request.Status, callback.Request.Status);
+
+ _clientMock.Verify(m => m.PatchAsync(
+ $"/zones/{ZoneId}/dnssec",
+ It.IsAny(),
+ It.IsAny()), Times.Once);
+ _clientMock.VerifyNoOtherCalls();
+ }
+
+ private ICloudflareClient GetClient()
+ {
+ _clientMock = new Mock();
+ _clientMock
+ .Setup(m => m.PatchAsync(
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny()))
+ .Callback((requestPath, request, _) => _callbacks.Add((requestPath, request)))
+ .ReturnsAsync(() => _response);
+
+ return _clientMock.Object;
+ }
+ }
+}