From adb7602e8086e7297fe6c2583b06f35ebea58a8d Mon Sep 17 00:00:00 2001 From: maciej-nedza <76946708+maciej-nedza@users.noreply.github.com> Date: Fri, 1 Dec 2023 10:36:40 +0100 Subject: [PATCH] [DE-585] Subscription status endpoints tests (#40) [DE-585] Subscription status endpoints tests --- doc/models/subscription.md | 2 +- .../advancedbilling/models/Subscription.java | 40 +++--- .../ProductsControllerCreateProductTest.java | 1 - ...tatusControllerCancelSubscriptionTest.java | 129 ++++++++++++++++++ ...StatusControllerPauseSubscriptionTest.java | 93 +++++++++++++ ...StatusControllerRetrySubscriptionTest.java | 56 ++++++++ .../SubscriptionStatusControllerTestBase.java | 90 ++++++++++++ ...ntrollerUpdateAutomaticResumptionTest.java | 94 +++++++++++++ ...scriptionStatusResumeSubscriptionTest.java | 63 +++++++++ 9 files changed, 547 insertions(+), 21 deletions(-) create mode 100644 tests/src/test/java/com/maxio/advancedbilling/controllers/subscriptionstatus/SubscriptionStatusControllerCancelSubscriptionTest.java create mode 100644 tests/src/test/java/com/maxio/advancedbilling/controllers/subscriptionstatus/SubscriptionStatusControllerPauseSubscriptionTest.java create mode 100644 tests/src/test/java/com/maxio/advancedbilling/controllers/subscriptionstatus/SubscriptionStatusControllerRetrySubscriptionTest.java create mode 100644 tests/src/test/java/com/maxio/advancedbilling/controllers/subscriptionstatus/SubscriptionStatusControllerTestBase.java create mode 100644 tests/src/test/java/com/maxio/advancedbilling/controllers/subscriptionstatus/SubscriptionStatusControllerUpdateAutomaticResumptionTest.java create mode 100644 tests/src/test/java/com/maxio/advancedbilling/controllers/subscriptionstatus/SubscriptionStatusResumeSubscriptionTest.java diff --git a/doc/models/subscription.md b/doc/models/subscription.md index ec5c4ea3..c351b51f 100644 --- a/doc/models/subscription.md +++ b/doc/models/subscription.md @@ -47,7 +47,7 @@ | `CouponUseCount` | `Integer` | Optional | (deprecated) How many times the subscription's single coupon has been used. This field has no replacement for multiple coupons. | Integer getCouponUseCount() | setCouponUseCount(Integer couponUseCount) | | `CouponUsesAllowed` | `Integer` | Optional | (deprecated) How many times the subscription's single coupon may be used. This field has no replacement for multiple coupons. | Integer getCouponUsesAllowed() | setCouponUsesAllowed(Integer couponUsesAllowed) | | `ReasonCode` | `String` | Optional | If the subscription is canceled, this is their churn code. | String getReasonCode() | setReasonCode(String reasonCode) | -| `AutomaticallyResumeAt` | `String` | Optional | The date the subscription is scheduled to automatically resume from the on_hold state. | String getAutomaticallyResumeAt() | setAutomaticallyResumeAt(String automaticallyResumeAt) | +| `AutomaticallyResumeAt` | `ZonedDateTime` | Optional | The date the subscription is scheduled to automatically resume from the on_hold state. | ZonedDateTime getAutomaticallyResumeAt() | setAutomaticallyResumeAt(ZonedDateTime automaticallyResumeAt) | | `CouponCodes` | `List` | Optional | An array for all the coupons attached to the subscription. | List getCouponCodes() | setCouponCodes(List couponCodes) | | `OfferId` | `Integer` | Optional | The ID of the offer associated with the subscription. | Integer getOfferId() | setOfferId(Integer offerId) | | `PayerId` | `Integer` | Optional | On Relationship Invoicing, the ID of the individual paying for the subscription. Defaults to the Customer ID unless the 'Customer Hierarchies & WhoPays' feature is enabled. | Integer getPayerId() | setPayerId(Integer payerId) | diff --git a/src/main/java/com/maxio/advancedbilling/models/Subscription.java b/src/main/java/com/maxio/advancedbilling/models/Subscription.java index 5a66ba31..65f965c1 100644 --- a/src/main/java/com/maxio/advancedbilling/models/Subscription.java +++ b/src/main/java/com/maxio/advancedbilling/models/Subscription.java @@ -58,7 +58,7 @@ public class Subscription { private OptionalNullable couponUseCount; private OptionalNullable couponUsesAllowed; private OptionalNullable reasonCode; - private OptionalNullable automaticallyResumeAt; + private OptionalNullable automaticallyResumeAt; private List couponCodes; private OptionalNullable offerId; private OptionalNullable payerId; @@ -129,7 +129,7 @@ public Subscription() { * @param couponUseCount Integer value for couponUseCount. * @param couponUsesAllowed Integer value for couponUsesAllowed. * @param reasonCode String value for reasonCode. - * @param automaticallyResumeAt String value for automaticallyResumeAt. + * @param automaticallyResumeAt ZonedDateTime value for automaticallyResumeAt. * @param couponCodes List of String value for couponCodes. * @param offerId Integer value for offerId. * @param payerId Integer value for payerId. @@ -192,7 +192,7 @@ public Subscription( Integer couponUseCount, Integer couponUsesAllowed, String reasonCode, - String automaticallyResumeAt, + ZonedDateTime automaticallyResumeAt, List couponCodes, Integer offerId, Integer payerId, @@ -317,7 +317,7 @@ public Subscription( * @param couponUseCount Integer value for couponUseCount. * @param couponUsesAllowed Integer value for couponUsesAllowed. * @param reasonCode String value for reasonCode. - * @param automaticallyResumeAt String value for automaticallyResumeAt. + * @param automaticallyResumeAt ZonedDateTime value for automaticallyResumeAt. * @param couponCodes List of String value for couponCodes. * @param offerId Integer value for offerId. * @param payerId Integer value for payerId. @@ -360,11 +360,12 @@ protected Subscription(Integer id, SubscriptionState state, Long balanceInCents, OptionalNullable paymentType, OptionalNullable referralCode, OptionalNullable nextProductId, OptionalNullable nextProductHandle, OptionalNullable couponUseCount, OptionalNullable couponUsesAllowed, - OptionalNullable reasonCode, OptionalNullable automaticallyResumeAt, - List couponCodes, OptionalNullable offerId, - OptionalNullable payerId, Long currentBillingAmountInCents, - Integer productPricePointId, String productPricePointType, - OptionalNullable nextProductPricePointId, OptionalNullable netTerms, + OptionalNullable reasonCode, + OptionalNullable automaticallyResumeAt, List couponCodes, + OptionalNullable offerId, OptionalNullable payerId, + Long currentBillingAmountInCents, Integer productPricePointId, + String productPricePointType, OptionalNullable nextProductPricePointId, + OptionalNullable netTerms, OptionalNullable storedCredentialTransactionId, OptionalNullable reference, OptionalNullable onHoldAt, Boolean prepaidDunning, List coupons, @@ -1691,31 +1692,32 @@ public void unsetReasonCode() { /** * Internal Getter for AutomaticallyResumeAt. * The date the subscription is scheduled to automatically resume from the on_hold state. - * @return Returns the Internal String + * @return Returns the Internal ZonedDateTime */ @JsonGetter("automatically_resume_at") @JsonInclude(JsonInclude.Include.NON_NULL) - @JsonSerialize(using = OptionalNullable.Serializer.class) - protected OptionalNullable internalGetAutomaticallyResumeAt() { + @JsonSerialize(using = OptionalNullable.Rfc8601DateTimeSerializer.class) + protected OptionalNullable internalGetAutomaticallyResumeAt() { return this.automaticallyResumeAt; } /** * Getter for AutomaticallyResumeAt. * The date the subscription is scheduled to automatically resume from the on_hold state. - * @return Returns the String + * @return Returns the ZonedDateTime */ - public String getAutomaticallyResumeAt() { + public ZonedDateTime getAutomaticallyResumeAt() { return OptionalNullable.getFrom(automaticallyResumeAt); } /** * Setter for AutomaticallyResumeAt. * The date the subscription is scheduled to automatically resume from the on_hold state. - * @param automaticallyResumeAt Value for String + * @param automaticallyResumeAt Value for ZonedDateTime */ @JsonSetter("automatically_resume_at") - public void setAutomaticallyResumeAt(String automaticallyResumeAt) { + @JsonDeserialize(using = DateTimeHelper.Rfc8601DateTimeDeserializer.class) + public void setAutomaticallyResumeAt(ZonedDateTime automaticallyResumeAt) { this.automaticallyResumeAt = OptionalNullable.of(automaticallyResumeAt); } @@ -2527,7 +2529,7 @@ public static class Builder { private OptionalNullable couponUseCount; private OptionalNullable couponUsesAllowed; private OptionalNullable reasonCode; - private OptionalNullable automaticallyResumeAt; + private OptionalNullable automaticallyResumeAt; private List couponCodes; private OptionalNullable offerId; private OptionalNullable payerId; @@ -3088,10 +3090,10 @@ public Builder unsetReasonCode() { /** * Setter for automaticallyResumeAt. - * @param automaticallyResumeAt String value for automaticallyResumeAt. + * @param automaticallyResumeAt ZonedDateTime value for automaticallyResumeAt. * @return Builder */ - public Builder automaticallyResumeAt(String automaticallyResumeAt) { + public Builder automaticallyResumeAt(ZonedDateTime automaticallyResumeAt) { this.automaticallyResumeAt = OptionalNullable.of(automaticallyResumeAt); return this; } diff --git a/tests/src/test/java/com/maxio/advancedbilling/controllers/products/ProductsControllerCreateProductTest.java b/tests/src/test/java/com/maxio/advancedbilling/controllers/products/ProductsControllerCreateProductTest.java index 1c4f6055..9534134c 100644 --- a/tests/src/test/java/com/maxio/advancedbilling/controllers/products/ProductsControllerCreateProductTest.java +++ b/tests/src/test/java/com/maxio/advancedbilling/controllers/products/ProductsControllerCreateProductTest.java @@ -13,7 +13,6 @@ import org.junit.jupiter.params.provider.MethodSource; import java.io.IOException; -import java.time.Instant; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; import java.util.Collections; diff --git a/tests/src/test/java/com/maxio/advancedbilling/controllers/subscriptionstatus/SubscriptionStatusControllerCancelSubscriptionTest.java b/tests/src/test/java/com/maxio/advancedbilling/controllers/subscriptionstatus/SubscriptionStatusControllerCancelSubscriptionTest.java new file mode 100644 index 00000000..4e1326ee --- /dev/null +++ b/tests/src/test/java/com/maxio/advancedbilling/controllers/subscriptionstatus/SubscriptionStatusControllerCancelSubscriptionTest.java @@ -0,0 +1,129 @@ +package com.maxio.advancedbilling.controllers.subscriptionstatus; + +import com.maxio.advancedbilling.exceptions.ApiException; +import com.maxio.advancedbilling.models.CancellationMethod; +import com.maxio.advancedbilling.models.CancellationOptions; +import com.maxio.advancedbilling.models.CancellationRequest; +import com.maxio.advancedbilling.models.Subscription; +import com.maxio.advancedbilling.models.SubscriptionState; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; + +import static com.maxio.advancedbilling.utils.CommonAssertions.assertNotFound; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +public class SubscriptionStatusControllerCancelSubscriptionTest extends SubscriptionStatusControllerTestBase { + + @Test + void shouldCancelActiveSubscription() throws IOException, ApiException { + // given + Subscription subscription = createSubscription(); + ZonedDateTime timestamp = ZonedDateTime.now().minus(5, ChronoUnit.SECONDS); + + // when + Subscription cancelledSubscription = subscriptionStatusController + .cancelSubscription(subscription.getId(), null).getSubscription(); + + // then + assertThat(cancelledSubscription).usingRecursiveComparison() + .ignoringFields("state", "updatedAt", "canceledAt", "cancelAtEndOfPeriod", + "cancellationMethod", "reasonCode") + .isEqualTo(subscription); + assertThat(cancelledSubscription.getState()).isEqualTo(SubscriptionState.CANCELED); + assertThat(cancelledSubscription.getUpdatedAt()).isAfter(timestamp); + assertThat(cancelledSubscription.getCanceledAt()).isAfter(timestamp); + assertThat(cancelledSubscription.getCancellationMethod()).isEqualTo(CancellationMethod.MERCHANT_API); + assertThat(cancelledSubscription.getReasonCode()) + .isEqualTo("CH:Unknown"); + } + + @Test + void shouldCancelActiveSubscriptionProvidingReasonCodeAndMessage() throws IOException, ApiException { + // given + Subscription subscription = createSubscription(); + ZonedDateTime timestamp = ZonedDateTime.now().minus(5, ChronoUnit.SECONDS); + String reasonCode = "CHURN"; + String cancellationMessage = "Customer left."; + CancellationRequest cancellationRequest = new CancellationRequest( + new CancellationOptions.Builder() + .cancellationMessage(cancellationMessage) + .reasonCode(reasonCode) + .build() + ); + + // when + Subscription cancelledSubscription = subscriptionStatusController + .cancelSubscription(subscription.getId(), cancellationRequest).getSubscription(); + + // then + assertThat(cancelledSubscription).usingRecursiveComparison() + .ignoringFields("state", "updatedAt", "canceledAt", "cancelAtEndOfPeriod", + "cancellationMethod", "reasonCode", "cancellationMessage") + .isEqualTo(subscription); + assertThat(cancelledSubscription.getState()).isEqualTo(SubscriptionState.CANCELED); + assertThat(cancelledSubscription.getUpdatedAt()).isAfter(timestamp); + assertThat(cancelledSubscription.getCanceledAt()).isAfter(timestamp); + assertThat(cancelledSubscription.getCancellationMethod()).isEqualTo(CancellationMethod.MERCHANT_API); + assertThat(cancelledSubscription.getReasonCode()).isEqualTo(reasonCode); + assertThat(cancelledSubscription.getCancellationMessage()).isEqualTo(cancellationMessage); + } + + @Test + void shouldNotRetryNonExistentSubscription() { + assertNotFound(() -> subscriptionStatusController.cancelSubscription(99999999, null), + "Not Found"); + } + + @Test + void shouldNotCancelAlreadyCanceledSubscriptionExceedingMessagesLimits() throws IOException, ApiException { + // given + Subscription subscription = createSubscription(); + + String reasonCode = "a".repeat(256); + String cancellationMessage = "a".repeat(65536); + CancellationRequest cancellationRequest = new CancellationRequest( + new CancellationOptions.Builder() + .cancellationMessage(cancellationMessage) + .reasonCode(reasonCode) + .build() + ); + + // when-then + assertThatExceptionOfType(ApiException.class) + .isThrownBy(() -> subscriptionStatusController.cancelSubscription(subscription.getId(), cancellationRequest)) + .withMessage("Unprocessable Entity (WebDAV)") + .satisfies(e -> { + assertThat(e.getResponseCode()).isEqualTo(422); + assertThat(e.getHttpContext().getResponse().getBody()).isEqualTo(""" + {"errors":["reason_code size cannot be greater than 255","cancellation_message size cannot be greater than 65535"]}""" + ); + }); + } + + @Test + void shouldNotCancelAlreadyCanceledSubscription() throws IOException, ApiException { + // given + Subscription subscription = createSubscription(); + + // when + subscriptionStatusController + .cancelSubscription(subscription.getId(), null); + + // then + assertThatExceptionOfType(ApiException.class) + .isThrownBy(() -> subscriptionStatusController.cancelSubscription(subscription.getId(), null) + ) + .withMessage("Unprocessable Entity (WebDAV)") + .satisfies(e -> { + assertThat(e.getResponseCode()).isEqualTo(422); + assertThat(e.getHttpContext().getResponse().getBody()).isEqualTo(""" + {"error":"The subscription is already canceled"}""" + ); + }); + } + +} diff --git a/tests/src/test/java/com/maxio/advancedbilling/controllers/subscriptionstatus/SubscriptionStatusControllerPauseSubscriptionTest.java b/tests/src/test/java/com/maxio/advancedbilling/controllers/subscriptionstatus/SubscriptionStatusControllerPauseSubscriptionTest.java new file mode 100644 index 00000000..32fd7d22 --- /dev/null +++ b/tests/src/test/java/com/maxio/advancedbilling/controllers/subscriptionstatus/SubscriptionStatusControllerPauseSubscriptionTest.java @@ -0,0 +1,93 @@ +package com.maxio.advancedbilling.controllers.subscriptionstatus; + +import com.maxio.advancedbilling.exceptions.ApiException; +import com.maxio.advancedbilling.exceptions.ErrorListResponseException; +import com.maxio.advancedbilling.models.AutoResume; +import com.maxio.advancedbilling.models.PauseRequest; +import com.maxio.advancedbilling.models.Subscription; +import com.maxio.advancedbilling.models.SubscriptionState; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; + +import static com.maxio.advancedbilling.utils.CommonAssertions.assertNotFound; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +public class SubscriptionStatusControllerPauseSubscriptionTest extends SubscriptionStatusControllerTestBase { + + @Test + void shouldPauseActiveSubscription() throws IOException, ApiException { + // given + Subscription subscription = createSubscription(); + ZonedDateTime timestamp = ZonedDateTime.now().minus(5, ChronoUnit.SECONDS); + + // when + Subscription pausedSubscription = subscriptionStatusController + .pauseSubscription(subscription.getId(), null).getSubscription(); + + // then + assertThat(pausedSubscription).usingRecursiveComparison() + .ignoringFields("state", "updatedAt", "onHoldAt", "prepaidDunning", + "productPricePointType") + .isEqualTo(subscription); + assertThat(pausedSubscription.getState()).isEqualTo(SubscriptionState.ON_HOLD); + assertThat(pausedSubscription.getUpdatedAt()).isAfter(timestamp); + assertThat(pausedSubscription.getOnHoldAt()).isAfter(timestamp); + } + + @Test + void shouldPauseActiveSubscriptionSettingAutomaticallyResumeAt() throws IOException, ApiException { + // given + Subscription subscription = createSubscription(); + ZonedDateTime timestamp = ZonedDateTime.now().minus(5, ChronoUnit.SECONDS); + ZonedDateTime resumeAt = ZonedDateTime.now().plusDays(3); + PauseRequest pauseRequest = new PauseRequest( + new AutoResume(resumeAt) + ); + + // when + Subscription pausedSubscription = subscriptionStatusController + .pauseSubscription(subscription.getId(), pauseRequest).getSubscription(); + + // then + assertThat(pausedSubscription).usingRecursiveComparison() + .ignoringFields("state", "updatedAt", "onHoldAt", "prepaidDunning", + "productPricePointType", "automaticallyResumeAt") + .isEqualTo(subscription); + assertThat(pausedSubscription.getState()).isEqualTo(SubscriptionState.ON_HOLD); + assertThat(pausedSubscription.getUpdatedAt()).isAfter(timestamp); + assertThat(pausedSubscription.getAutomaticallyResumeAt().toEpochSecond()) + .isEqualTo(resumeAt.toEpochSecond()); + assertThat(pausedSubscription.getOnHoldAt()).isAfter(timestamp); + } + + @Test + void shouldNotPauseAlreadyPausedSubscription() throws IOException, ApiException { + // given + Subscription subscription = createSubscription(); + + // when + subscriptionStatusController + .pauseSubscription(subscription.getId(), null); + + // when-then + assertThatExceptionOfType(ErrorListResponseException.class) + .isThrownBy(() -> subscriptionStatusController.pauseSubscription(subscription.getId(), null) + ) + .withMessage("Unprocessable Entity (WebDAV)") + .satisfies(e -> { + assertThat(e.getResponseCode()).isEqualTo(422); + assertThat(e.getErrors()).containsExactlyInAnyOrder( + "This subscription is not eligible to be put on hold."); + }); + } + + @Test + void shouldNotPauseNonExistentSubscription() { + assertNotFound(() -> subscriptionStatusController.pauseSubscription(99999999, null)); + } + +} diff --git a/tests/src/test/java/com/maxio/advancedbilling/controllers/subscriptionstatus/SubscriptionStatusControllerRetrySubscriptionTest.java b/tests/src/test/java/com/maxio/advancedbilling/controllers/subscriptionstatus/SubscriptionStatusControllerRetrySubscriptionTest.java new file mode 100644 index 00000000..f78f792c --- /dev/null +++ b/tests/src/test/java/com/maxio/advancedbilling/controllers/subscriptionstatus/SubscriptionStatusControllerRetrySubscriptionTest.java @@ -0,0 +1,56 @@ +package com.maxio.advancedbilling.controllers.subscriptionstatus; + +import com.maxio.advancedbilling.exceptions.ApiException; +import com.maxio.advancedbilling.exceptions.ErrorListResponseException; +import com.maxio.advancedbilling.models.Subscription; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static com.maxio.advancedbilling.utils.CommonAssertions.assertNotFound; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +public class SubscriptionStatusControllerRetrySubscriptionTest extends SubscriptionStatusControllerTestBase { + + @Test + void shouldRetryFailedSubscription() throws IOException, ApiException { + // given + Subscription subscription = createSubscription(); + + // when + Subscription retriedSubscription = subscriptionStatusController.retrySubscription(subscription.getId()) + .getSubscription(); + + // then + assertThat(retriedSubscription).usingRecursiveComparison() + .ignoringFields("productPricePointType", "prepaidDunning", "updatedAt") + .isEqualTo(subscription); + } + + @Test + void shouldNotRetryCancelledSubscription() throws IOException, ApiException { + // given + Subscription subscription = createSubscription(); + + // when + subscriptionStatusController + .cancelSubscription(subscription.getId(), null); + + // then + assertThatExceptionOfType(ErrorListResponseException.class) + .isThrownBy(() -> subscriptionStatusController.retrySubscription(subscription.getId()) + ) + .withMessage("Unprocessable Entity (WebDAV)") + .satisfies(e -> { + assertThat(e.getResponseCode()).isEqualTo(422); + assertThat(e.getErrors()).isEmpty(); + }); + } + + @Test + void shouldNotRetryNonExistentSubscription() { + assertNotFound(() -> subscriptionStatusController.retrySubscription(99999999)); + } + +} diff --git a/tests/src/test/java/com/maxio/advancedbilling/controllers/subscriptionstatus/SubscriptionStatusControllerTestBase.java b/tests/src/test/java/com/maxio/advancedbilling/controllers/subscriptionstatus/SubscriptionStatusControllerTestBase.java new file mode 100644 index 00000000..8c41cc33 --- /dev/null +++ b/tests/src/test/java/com/maxio/advancedbilling/controllers/subscriptionstatus/SubscriptionStatusControllerTestBase.java @@ -0,0 +1,90 @@ +package com.maxio.advancedbilling.controllers.subscriptionstatus; + +import com.maxio.advancedbilling.TestClient; +import com.maxio.advancedbilling.controllers.ProductFamiliesController; +import com.maxio.advancedbilling.controllers.ProductsController; +import com.maxio.advancedbilling.controllers.SubscriptionStatusController; +import com.maxio.advancedbilling.controllers.SubscriptionsController; +import com.maxio.advancedbilling.exceptions.ApiException; +import com.maxio.advancedbilling.models.CreateCustomer; +import com.maxio.advancedbilling.models.CreateCustomerRequest; +import com.maxio.advancedbilling.models.CreateOrUpdateProduct; +import com.maxio.advancedbilling.models.CreateOrUpdateProductRequest; +import com.maxio.advancedbilling.models.CreateProductFamily; +import com.maxio.advancedbilling.models.CreateProductFamilyRequest; +import com.maxio.advancedbilling.models.CreateSubscription; +import com.maxio.advancedbilling.models.CreateSubscriptionRequest; +import com.maxio.advancedbilling.models.Customer; +import com.maxio.advancedbilling.models.IntervalUnit; +import com.maxio.advancedbilling.models.PaymentProfileAttributes; +import com.maxio.advancedbilling.models.ProductFamily; +import com.maxio.advancedbilling.models.Subscription; +import com.maxio.advancedbilling.models.containers.PaymentProfileAttributesExpirationMonth; +import com.maxio.advancedbilling.models.containers.PaymentProfileAttributesExpirationYear; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.BeforeAll; + +import java.io.IOException; + +abstract class SubscriptionStatusControllerTestBase { + + protected static final ProductsController productsController = TestClient.createClient().getProductsController(); + protected static final SubscriptionsController subscriptionsController = TestClient.createClient().getSubscriptionsController(); + protected static final SubscriptionStatusController subscriptionStatusController = TestClient.createClient().getSubscriptionStatusController(); + + + protected static int productId; + protected static Customer customer; + protected static Subscription subscription; + + @BeforeAll + static void setup() throws IOException, ApiException { + ProductFamiliesController productFamiliesController = TestClient.createClient() + .getProductFamiliesController(); + String seed = RandomStringUtils.randomAlphanumeric(5).toLowerCase(); + + ProductFamily productFamily = productFamiliesController.createProductFamily(new CreateProductFamilyRequest( + new CreateProductFamily("Test Product Family " + seed, null))) + .getProductFamily(); + + productId = productsController + .createProduct(productFamily.getId(), new CreateOrUpdateProductRequest( + new CreateOrUpdateProduct.Builder() + .name("Initial Sample product-" + RandomStringUtils.randomAlphanumeric(5)) + .handle("initial-sample-product-" + seed) + .priceInCents(1000) + .interval(1) + .intervalUnit(IntervalUnit.MONTH) + .build() + )) + .getProduct().getId(); + + customer = TestClient.createClient().getCustomersController() + .createCustomer(new CreateCustomerRequest(new CreateCustomer.Builder() + .firstName("Joe") + .lastName("Blow") + .email("joe@example.com") + .build()) + ) + .getCustomer(); + } + + Subscription createSubscription() throws IOException, ApiException { + return subscriptionsController.createSubscription( + new CreateSubscriptionRequest( + new CreateSubscription.Builder() + .productId(productId) + .customerId(customer.getId()) + .creditCardAttributes( + new PaymentProfileAttributes.Builder() + .expirationMonth(PaymentProfileAttributesExpirationMonth.fromNumber(12)) + .expirationYear(PaymentProfileAttributesExpirationYear.fromNumber(2024)) + .fullNumber("1") + .build() + ) + .build() + ) + ).getSubscription(); + } + +} diff --git a/tests/src/test/java/com/maxio/advancedbilling/controllers/subscriptionstatus/SubscriptionStatusControllerUpdateAutomaticResumptionTest.java b/tests/src/test/java/com/maxio/advancedbilling/controllers/subscriptionstatus/SubscriptionStatusControllerUpdateAutomaticResumptionTest.java new file mode 100644 index 00000000..41e9f280 --- /dev/null +++ b/tests/src/test/java/com/maxio/advancedbilling/controllers/subscriptionstatus/SubscriptionStatusControllerUpdateAutomaticResumptionTest.java @@ -0,0 +1,94 @@ +package com.maxio.advancedbilling.controllers.subscriptionstatus; + +import com.maxio.advancedbilling.exceptions.ApiException; +import com.maxio.advancedbilling.exceptions.ErrorListResponseException; +import com.maxio.advancedbilling.models.AutoResume; +import com.maxio.advancedbilling.models.PauseRequest; +import com.maxio.advancedbilling.models.Subscription; +import com.maxio.advancedbilling.models.SubscriptionState; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; + +import static com.maxio.advancedbilling.utils.CommonAssertions.assertNotFound; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +public class SubscriptionStatusControllerUpdateAutomaticResumptionTest extends SubscriptionStatusControllerTestBase { + + @Test + void shouldUpdatePausedSubscriptionAutomaticResumptionDate() throws IOException, ApiException { + // given + Subscription subscription = createSubscription(); + ZonedDateTime timestamp = ZonedDateTime.now().minus(5, ChronoUnit.SECONDS); + ZonedDateTime resumeAt = ZonedDateTime.now().plusDays(3); + PauseRequest pauseRequest = new PauseRequest( + new AutoResume(resumeAt) + ); + + // when + subscriptionStatusController.pauseSubscription(subscription.getId(), null); + Subscription pausedSubscription = subscriptionStatusController + .updateAutomaticSubscriptionResumption(subscription.getId(), pauseRequest).getSubscription(); + + // then + assertThat(pausedSubscription).usingRecursiveComparison() + .ignoringFields("state", "updatedAt", "onHoldAt", "prepaidDunning", + "productPricePointType", "automaticallyResumeAt") + .isEqualTo(subscription); + assertThat(pausedSubscription.getState()).isEqualTo(SubscriptionState.ON_HOLD); + assertThat(pausedSubscription.getUpdatedAt()).isAfter(timestamp); + assertThat(pausedSubscription.getAutomaticallyResumeAt().toEpochSecond()) + .isEqualTo(resumeAt.toEpochSecond()); + } + + @Test + void shouldNotUpdatePausedSubscriptionAutomaticResumptionDateToDateInPast() throws IOException, ApiException { + // given + Subscription subscription = createSubscription(); + PauseRequest pauseRequest = new PauseRequest( + new AutoResume(ZonedDateTime.now().minusMonths(2)) + ); + + // when + subscriptionStatusController + .pauseSubscription(subscription.getId(), null); + + // then + assertThatExceptionOfType(ErrorListResponseException.class) + .isThrownBy(() -> subscriptionStatusController + .updateAutomaticSubscriptionResumption(subscription.getId(), pauseRequest) + ) + .withMessage("Unprocessable Entity (WebDAV)") + .satisfies(e -> { + assertThat(e.getResponseCode()).isEqualTo(422); + assertThat(e.getErrors()).containsExactlyInAnyOrder( + "Automatic resume date: must be at least 24 hours in the future."); + }); + } + + @Test + void shouldNotUpdateActiveSubscriptionAutomaticResumptionDate() throws IOException, ApiException { + // given + Subscription subscription = createSubscription(); + + // when-then + assertThatExceptionOfType(ErrorListResponseException.class) + .isThrownBy(() -> subscriptionStatusController.updateAutomaticSubscriptionResumption(subscription.getId(), null) + ) + .withMessage("Unprocessable Entity (WebDAV)") + .satisfies(e -> { + assertThat(e.getResponseCode()).isEqualTo(422); + assertThat(e.getErrors()).containsExactlyInAnyOrder( + "Subscription is not currently on hold."); + }); + } + + @Test + void shouldNotUpdateNonExistentSubscriptionAutomaticResumptionDate() { + assertNotFound(() -> subscriptionStatusController.updateAutomaticSubscriptionResumption(99999999, null)); + } + +} diff --git a/tests/src/test/java/com/maxio/advancedbilling/controllers/subscriptionstatus/SubscriptionStatusResumeSubscriptionTest.java b/tests/src/test/java/com/maxio/advancedbilling/controllers/subscriptionstatus/SubscriptionStatusResumeSubscriptionTest.java new file mode 100644 index 00000000..d36cfded --- /dev/null +++ b/tests/src/test/java/com/maxio/advancedbilling/controllers/subscriptionstatus/SubscriptionStatusResumeSubscriptionTest.java @@ -0,0 +1,63 @@ +package com.maxio.advancedbilling.controllers.subscriptionstatus; + +import com.maxio.advancedbilling.exceptions.ApiException; +import com.maxio.advancedbilling.exceptions.ErrorListResponseException; +import com.maxio.advancedbilling.models.Subscription; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; + +import static com.maxio.advancedbilling.models.SubscriptionState.ACTIVE; +import static com.maxio.advancedbilling.models.SubscriptionState.ON_HOLD; +import static com.maxio.advancedbilling.utils.CommonAssertions.assertNotFound; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +public class SubscriptionStatusResumeSubscriptionTest extends SubscriptionStatusControllerTestBase { + + @Test + void shouldResumePausedSubscription() throws IOException, ApiException { + // given + Subscription subscription = createSubscription(); + ZonedDateTime timestamp = ZonedDateTime.now().minus(5, ChronoUnit.SECONDS); + + // when + subscriptionStatusController.pauseSubscription(subscription.getId(), null); + Subscription resumedSubscription = subscriptionStatusController + .resumeSubscription(subscription.getId(), null).getSubscription(); + + // then + assertThat(resumedSubscription).usingRecursiveComparison() + .ignoringFields("updatedAt", "prepaidDunning", "previousState", "productPricePointType") + .isEqualTo(subscription); + assertThat(resumedSubscription.getUpdatedAt()).isAfter(timestamp); + assertThat(resumedSubscription.getState()).isEqualTo(ACTIVE); + assertThat(resumedSubscription.getPreviousState()).isEqualTo(ON_HOLD); + } + + @Test + void shouldNotResumeActiveSubscription() throws IOException, ApiException { + // given + Subscription subscription = createSubscription(); + + // when-then + assertThatExceptionOfType(ErrorListResponseException.class) + .isThrownBy(() -> subscriptionStatusController + .resumeSubscription(subscription.getId(), null) + ) + .withMessage("Unprocessable Entity (WebDAV)") + .satisfies(e -> { + assertThat(e.getResponseCode()).isEqualTo(422); + assertThat(e.getErrors()).containsExactlyInAnyOrder( + "Only subscriptions that are on hold can be resumed."); + }); + } + + @Test + void shouldNotResumeNonExistentSubscription() { + assertNotFound(() -> subscriptionStatusController.resumeSubscription(99999999, null)); + } + +}