diff --git a/Extensions/Cloudflare.Zones/Internals/InternalCreateZoneSubscriptionRequest.cs b/Extensions/Cloudflare.Zones/Internals/InternalCreateZoneSubscriptionRequest.cs new file mode 100644 index 0000000..c20565c --- /dev/null +++ b/Extensions/Cloudflare.Zones/Internals/InternalCreateZoneSubscriptionRequest.cs @@ -0,0 +1,11 @@ +namespace AMWD.Net.Api.Cloudflare.Zones.Internals +{ + internal class InternalCreateZoneSubscriptionRequest + { + [JsonProperty("frequency")] + public RenewFrequency? Frequency { get; set; } + + [JsonProperty("rate_plan")] + public RatePlan? RatePlan { get; set; } + } +} diff --git a/Extensions/Cloudflare.Zones/Internals/InternalUpdateZoneSubscriptionRequest.cs b/Extensions/Cloudflare.Zones/Internals/InternalUpdateZoneSubscriptionRequest.cs new file mode 100644 index 0000000..b0c62c0 --- /dev/null +++ b/Extensions/Cloudflare.Zones/Internals/InternalUpdateZoneSubscriptionRequest.cs @@ -0,0 +1,11 @@ +namespace AMWD.Net.Api.Cloudflare.Zones.Internals +{ + internal class InternalUpdateZoneSubscriptionRequest + { + [JsonProperty("frequency")] + public RenewFrequency? Frequency { get; set; } + + [JsonProperty("rate_plan")] + public RatePlan? RatePlan { get; set; } + } +} diff --git a/Extensions/Cloudflare.Zones/README.md b/Extensions/Cloudflare.Zones/README.md index cf41503..8f4ce49 100644 --- a/Extensions/Cloudflare.Zones/README.md +++ b/Extensions/Cloudflare.Zones/README.md @@ -52,6 +52,13 @@ This package contains the feature set of the _Domain/Zone Management_ section of - **DEPRECATED** [Get All Zone Settings](https://developers.cloudflare.com/api/resources/zones/subresources/settings/methods/list/) +##### [Subscriptions] + +- [Create Zone Subscription](https://developers.cloudflare.com/api/resources/zones/subresources/subscriptions/methods/create/) +- [Zone Subscription Details](https://developers.cloudflare.com/api/resources/zones/subresources/subscriptions/methods/get/) +- [Update Zone Subscription](https://developers.cloudflare.com/api/resources/zones/subresources/subscriptions/methods/update/) + + --- @@ -68,3 +75,4 @@ Published under MIT License (see [choose a license]) [Plans]: https://developers.cloudflare.com/api/resources/zones/subresources/plans/ [Rate Plans]: https://developers.cloudflare.com/api/resources/zones/subresources/rate_plans/ [Settings]: https://developers.cloudflare.com/api/resources/zones/subresources/settings/ +[Subscriptions]: https://developers.cloudflare.com/api/resources/zones/subresources/subscriptions/ diff --git a/Extensions/Cloudflare.Zones/Requests/CreateZoneSubscriptionRequest.cs b/Extensions/Cloudflare.Zones/Requests/CreateZoneSubscriptionRequest.cs new file mode 100644 index 0000000..29129ab --- /dev/null +++ b/Extensions/Cloudflare.Zones/Requests/CreateZoneSubscriptionRequest.cs @@ -0,0 +1,32 @@ +namespace AMWD.Net.Api.Cloudflare.Zones +{ + /// + /// Represents a request to create a zone subscription. + /// + public class CreateZoneSubscriptionRequest + { + /// + /// Initializes a new instance of the class. + /// + /// The zone identifier. + public CreateZoneSubscriptionRequest(string zoneId) + { + ZoneId = zoneId; + } + + /// + /// The zone identifier. + /// + public string ZoneId { get; set; } + + /// + /// How often the subscription is renewed automatically. + /// + public RenewFrequency? Frequency { get; set; } + + /// + /// The rate plan applied to the subscription. + /// + public RatePlan? RatePlan { get; set; } + } +} diff --git a/Extensions/Cloudflare.Zones/Requests/UpdateZoneSubscriptionRequest.cs b/Extensions/Cloudflare.Zones/Requests/UpdateZoneSubscriptionRequest.cs new file mode 100644 index 0000000..00b67d9 --- /dev/null +++ b/Extensions/Cloudflare.Zones/Requests/UpdateZoneSubscriptionRequest.cs @@ -0,0 +1,32 @@ +namespace AMWD.Net.Api.Cloudflare.Zones +{ + /// + /// Represents a request to update a zone subscription. + /// + public class UpdateZoneSubscriptionRequest + { + /// + /// Initializes a new instance of the class. + /// + /// The zone identifier. + public UpdateZoneSubscriptionRequest(string zoneId) + { + ZoneId = zoneId; + } + + /// + /// The zone identifier. + /// + public string ZoneId { get; set; } + + /// + /// How often the subscription is renewed automatically. + /// + public RenewFrequency? Frequency { get; set; } + + /// + /// The rate plan applied to the subscription. + /// + public RatePlan? RatePlan { get; set; } + } +} diff --git a/Extensions/Cloudflare.Zones/ZoneSubscriptionsExtensions.cs b/Extensions/Cloudflare.Zones/ZoneSubscriptionsExtensions.cs new file mode 100644 index 0000000..5565682 --- /dev/null +++ b/Extensions/Cloudflare.Zones/ZoneSubscriptionsExtensions.cs @@ -0,0 +1,63 @@ +using System.Threading; +using System.Threading.Tasks; +using AMWD.Net.Api.Cloudflare.Zones.Internals; + +namespace AMWD.Net.Api.Cloudflare.Zones +{ + /// + /// Extensions for Zone Subscriptions. + /// + public static class ZoneSubscriptionsExtensions + { + /// + /// Create a zone subscription, either plan or add-ons. + /// + /// The instance. + /// The request. + /// A cancellation token used to propagate notification that this operation should be canceled. + public static Task> CreateZoneSubscription(this ICloudflareClient client, CreateZoneSubscriptionRequest request, CancellationToken cancellationToken = default) + { + request.ZoneId.ValidateCloudflareId(); + + var req = new InternalCreateZoneSubscriptionRequest + { + Frequency = request.Frequency, + RatePlan = request.RatePlan + }; + + return client.PostAsync($"/zones/{request.ZoneId}/subscription", req, null, cancellationToken); + } + + /// + /// Lists zone subscription details. + /// + /// The instance. + /// The zone identifier. + /// A cancellation token used to propagate notification that this operation should be canceled. + public static Task> ZoneSubscriptionDetails(this ICloudflareClient client, string zoneId, CancellationToken cancellationToken = default) + { + zoneId.ValidateCloudflareId(); + + return client.GetAsync($"/zones/{zoneId}/subscription", null, cancellationToken); + } + + /// + /// Updates zone subscriptions, either plan or add-ons. + /// + /// The instance. + /// The request. + /// A cancellation token used to propagate notification that this operation should be canceled. + public static Task> UpdateZoneSubscription(this ICloudflareClient client, UpdateZoneSubscriptionRequest request, CancellationToken cancellationToken = default) + { + request.ZoneId.ValidateCloudflareId(); + + var req = new InternalUpdateZoneSubscriptionRequest + { + Frequency = request.Frequency, + RatePlan = request.RatePlan + }; + + return client.PutAsync($"/zones/{request.ZoneId}/subscription", req, cancellationToken); + } + } +} diff --git a/UnitTests/Cloudflare.Zones.Tests/ZoneSubscriptionsExtensions/CreateZoneSubscriptionTest.cs b/UnitTests/Cloudflare.Zones.Tests/ZoneSubscriptionsExtensions/CreateZoneSubscriptionTest.cs new file mode 100644 index 0000000..9721a6f --- /dev/null +++ b/UnitTests/Cloudflare.Zones.Tests/ZoneSubscriptionsExtensions/CreateZoneSubscriptionTest.cs @@ -0,0 +1,90 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using AMWD.Net.Api.Cloudflare; +using AMWD.Net.Api.Cloudflare.Zones; +using AMWD.Net.Api.Cloudflare.Zones.Internals; +using Moq; + +namespace Cloudflare.Zones.Tests.ZoneSubscriptionsExtensions +{ + [TestClass] + public class CreateZoneSubscriptionTest + { + private const string ZoneId = "023e105f4ecef8ad9ca31a8372d0c353"; + + private Mock _clientMock; + + private CloudflareResponse _response; + + private List<(string RequestPath, InternalCreateZoneSubscriptionRequest Request)> _callbacks; + + private CreateZoneSubscriptionRequest _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 Subscription() + }; + + _request = new CreateZoneSubscriptionRequest(ZoneId) + { + Frequency = RenewFrequency.Quarterly, + RatePlan = new RatePlan + { + Id = RatePlanId.Business, + PublicName = "Business Plan" + } + }; + } + + [TestMethod] + public async Task ShouldCreateZoneSubscription() + { + // Arrange + var client = GetClient(); + + // Act + var response = await client.CreateZoneSubscription(_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}/subscription", callback.RequestPath); + Assert.IsNotNull(callback.Request); + + Assert.AreEqual(_request.Frequency, callback.Request.Frequency); + Assert.AreEqual(_request.RatePlan, callback.Request.RatePlan); + + _clientMock.Verify(m => m.PostAsync($"/zones/{ZoneId}/subscription", It.IsAny(), null, It.IsAny()), Times.Once); + _clientMock.VerifyNoOtherCalls(); + } + + private ICloudflareClient GetClient() + { + _clientMock = new Mock(); + _clientMock + .Setup(m => m.PostAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((requestPath, request, _, _) => _callbacks.Add((requestPath, request))) + .ReturnsAsync(() => _response); + + return _clientMock.Object; + } + } +} diff --git a/UnitTests/Cloudflare.Zones.Tests/ZoneSubscriptionsExtensions/UpdateZoneSubscriptionTest.cs b/UnitTests/Cloudflare.Zones.Tests/ZoneSubscriptionsExtensions/UpdateZoneSubscriptionTest.cs new file mode 100644 index 0000000..aa31482 --- /dev/null +++ b/UnitTests/Cloudflare.Zones.Tests/ZoneSubscriptionsExtensions/UpdateZoneSubscriptionTest.cs @@ -0,0 +1,90 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using AMWD.Net.Api.Cloudflare; +using AMWD.Net.Api.Cloudflare.Zones; +using AMWD.Net.Api.Cloudflare.Zones.Internals; +using Moq; + +namespace Cloudflare.Zones.Tests.ZoneSubscriptionsExtensions +{ + [TestClass] + public class UpdateZoneSubscriptionTest + { + private const string ZoneId = "023e105f4ecef8ad9ca31a8372d0c353"; + + private Mock _clientMock; + + private CloudflareResponse _response; + + private List<(string RequestPath, InternalUpdateZoneSubscriptionRequest Request)> _callbacks; + + private UpdateZoneSubscriptionRequest _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 Subscription() + }; + + _request = new UpdateZoneSubscriptionRequest(ZoneId) + { + Frequency = RenewFrequency.Quarterly, + RatePlan = new RatePlan + { + Id = RatePlanId.Business, + PublicName = "Business Plan" + } + }; + } + + [TestMethod] + public async Task ShouldUpdateZoneSubscription() + { + // Arrange + var client = GetClient(); + + // Act + var response = await client.UpdateZoneSubscription(_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}/subscription", callback.RequestPath); + Assert.IsNotNull(callback.Request); + + Assert.AreEqual(_request.Frequency, callback.Request.Frequency); + Assert.AreEqual(_request.RatePlan, callback.Request.RatePlan); + + _clientMock.Verify(m => m.PutAsync($"/zones/{ZoneId}/subscription", It.IsAny(), It.IsAny()), Times.Once); + _clientMock.VerifyNoOtherCalls(); + } + + private ICloudflareClient GetClient() + { + _clientMock = new Mock(); + _clientMock + .Setup(m => m.PutAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((requestPath, request, _) => _callbacks.Add((requestPath, request))) + .ReturnsAsync(() => _response); + + return _clientMock.Object; + } + } +} diff --git a/UnitTests/Cloudflare.Zones.Tests/ZoneSubscriptionsExtensions/ZoneSubscriptionDetailsTest.cs b/UnitTests/Cloudflare.Zones.Tests/ZoneSubscriptionsExtensions/ZoneSubscriptionDetailsTest.cs new file mode 100644 index 0000000..1cedbea --- /dev/null +++ b/UnitTests/Cloudflare.Zones.Tests/ZoneSubscriptionsExtensions/ZoneSubscriptionDetailsTest.cs @@ -0,0 +1,74 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using AMWD.Net.Api.Cloudflare; +using AMWD.Net.Api.Cloudflare.Zones; +using Moq; + +namespace Cloudflare.Zones.Tests.ZoneSubscriptionsExtensions +{ + [TestClass] + public class ZoneSubscriptionDetailsTest + { + 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 Subscription() + }; + } + + [TestMethod] + public async Task ShouldGetZoneSubscriptionDetails() + { + // Arrange + var client = GetClient(); + + // Act + var response = await client.ZoneSubscriptionDetails(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}/subscription", callback.RequestPath); + Assert.IsNull(callback.QueryFilter); + + _clientMock.Verify(m => m.GetAsync($"/zones/{ZoneId}/subscription", 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; + } + } +}