Skip to content

Commit

Permalink
[DE-585] Subscription status endpoints tests (#40)
Browse files Browse the repository at this point in the history
[DE-585] Subscription status endpoints tests
  • Loading branch information
maciej-nedza authored Dec 1, 2023
1 parent 0991bb4 commit adb7602
Show file tree
Hide file tree
Showing 9 changed files with 547 additions and 21 deletions.
2 changes: 1 addition & 1 deletion doc/models/subscription.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>` | Optional | An array for all the coupons attached to the subscription. | List<String> getCouponCodes() | setCouponCodes(List<String> 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) |
Expand Down
40 changes: 21 additions & 19 deletions src/main/java/com/maxio/advancedbilling/models/Subscription.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public class Subscription {
private OptionalNullable<Integer> couponUseCount;
private OptionalNullable<Integer> couponUsesAllowed;
private OptionalNullable<String> reasonCode;
private OptionalNullable<String> automaticallyResumeAt;
private OptionalNullable<ZonedDateTime> automaticallyResumeAt;
private List<String> couponCodes;
private OptionalNullable<Integer> offerId;
private OptionalNullable<Integer> payerId;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -192,7 +192,7 @@ public Subscription(
Integer couponUseCount,
Integer couponUsesAllowed,
String reasonCode,
String automaticallyResumeAt,
ZonedDateTime automaticallyResumeAt,
List<String> couponCodes,
Integer offerId,
Integer payerId,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -360,11 +360,12 @@ protected Subscription(Integer id, SubscriptionState state, Long balanceInCents,
OptionalNullable<String> paymentType, OptionalNullable<String> referralCode,
OptionalNullable<Integer> nextProductId, OptionalNullable<String> nextProductHandle,
OptionalNullable<Integer> couponUseCount, OptionalNullable<Integer> couponUsesAllowed,
OptionalNullable<String> reasonCode, OptionalNullable<String> automaticallyResumeAt,
List<String> couponCodes, OptionalNullable<Integer> offerId,
OptionalNullable<Integer> payerId, Long currentBillingAmountInCents,
Integer productPricePointId, String productPricePointType,
OptionalNullable<Integer> nextProductPricePointId, OptionalNullable<Integer> netTerms,
OptionalNullable<String> reasonCode,
OptionalNullable<ZonedDateTime> automaticallyResumeAt, List<String> couponCodes,
OptionalNullable<Integer> offerId, OptionalNullable<Integer> payerId,
Long currentBillingAmountInCents, Integer productPricePointId,
String productPricePointType, OptionalNullable<Integer> nextProductPricePointId,
OptionalNullable<Integer> netTerms,
OptionalNullable<Integer> storedCredentialTransactionId,
OptionalNullable<String> reference, OptionalNullable<ZonedDateTime> onHoldAt,
Boolean prepaidDunning, List<SubscriptionIncludedCoupon> coupons,
Expand Down Expand Up @@ -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<String> internalGetAutomaticallyResumeAt() {
@JsonSerialize(using = OptionalNullable.Rfc8601DateTimeSerializer.class)
protected OptionalNullable<ZonedDateTime> 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);
}

Expand Down Expand Up @@ -2527,7 +2529,7 @@ public static class Builder {
private OptionalNullable<Integer> couponUseCount;
private OptionalNullable<Integer> couponUsesAllowed;
private OptionalNullable<String> reasonCode;
private OptionalNullable<String> automaticallyResumeAt;
private OptionalNullable<ZonedDateTime> automaticallyResumeAt;
private List<String> couponCodes;
private OptionalNullable<Integer> offerId;
private OptionalNullable<Integer> payerId;
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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"}"""
);
});
}

}
Original file line number Diff line number Diff line change
@@ -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));
}

}
Loading

0 comments on commit adb7602

Please sign in to comment.