diff --git a/tests/pom.xml b/tests/pom.xml
index 18dbd13a..911f90c0 100644
--- a/tests/pom.xml
+++ b/tests/pom.xml
@@ -6,8 +6,8 @@
jar
AdvancedBillingTests
- 1.8
- 1.8
+ 17
+ 17
UTF-8
diff --git a/tests/src/test/java/com/maxio/advancedbilling/controllers/CustomersControllerCreateTest.java b/tests/src/test/java/com/maxio/advancedbilling/controllers/customers/CustomersControllerCreateTest.java
similarity index 98%
rename from tests/src/test/java/com/maxio/advancedbilling/controllers/CustomersControllerCreateTest.java
rename to tests/src/test/java/com/maxio/advancedbilling/controllers/customers/CustomersControllerCreateTest.java
index 4fb45095..98e5015e 100644
--- a/tests/src/test/java/com/maxio/advancedbilling/controllers/CustomersControllerCreateTest.java
+++ b/tests/src/test/java/com/maxio/advancedbilling/controllers/customers/CustomersControllerCreateTest.java
@@ -1,6 +1,7 @@
-package com.maxio.advancedbilling.controllers;
+package com.maxio.advancedbilling.controllers.customers;
import com.maxio.advancedbilling.TestClient;
+import com.maxio.advancedbilling.controllers.CustomersController;
import com.maxio.advancedbilling.exceptions.ApiException;
import com.maxio.advancedbilling.exceptions.CustomerErrorResponseException;
import com.maxio.advancedbilling.models.CreateCustomer;
diff --git a/tests/src/test/java/com/maxio/advancedbilling/controllers/CustomersControllerDeleteTest.java b/tests/src/test/java/com/maxio/advancedbilling/controllers/customers/CustomersControllerDeleteTest.java
similarity index 92%
rename from tests/src/test/java/com/maxio/advancedbilling/controllers/CustomersControllerDeleteTest.java
rename to tests/src/test/java/com/maxio/advancedbilling/controllers/customers/CustomersControllerDeleteTest.java
index 8747f3b1..14e385e0 100644
--- a/tests/src/test/java/com/maxio/advancedbilling/controllers/CustomersControllerDeleteTest.java
+++ b/tests/src/test/java/com/maxio/advancedbilling/controllers/customers/CustomersControllerDeleteTest.java
@@ -1,6 +1,7 @@
-package com.maxio.advancedbilling.controllers;
+package com.maxio.advancedbilling.controllers.customers;
import com.maxio.advancedbilling.TestClient;
+import com.maxio.advancedbilling.controllers.CustomersController;
import com.maxio.advancedbilling.exceptions.ApiException;
import com.maxio.advancedbilling.models.CreateCustomer;
import com.maxio.advancedbilling.models.CreateCustomerRequest;
diff --git a/tests/src/test/java/com/maxio/advancedbilling/controllers/CustomersControllerListOrFindTest.java b/tests/src/test/java/com/maxio/advancedbilling/controllers/customers/CustomersControllerListOrFindTest.java
similarity index 99%
rename from tests/src/test/java/com/maxio/advancedbilling/controllers/CustomersControllerListOrFindTest.java
rename to tests/src/test/java/com/maxio/advancedbilling/controllers/customers/CustomersControllerListOrFindTest.java
index 2f0bbbd2..94ca9765 100644
--- a/tests/src/test/java/com/maxio/advancedbilling/controllers/CustomersControllerListOrFindTest.java
+++ b/tests/src/test/java/com/maxio/advancedbilling/controllers/customers/CustomersControllerListOrFindTest.java
@@ -1,6 +1,7 @@
-package com.maxio.advancedbilling.controllers;
+package com.maxio.advancedbilling.controllers.customers;
import com.maxio.advancedbilling.TestClient;
+import com.maxio.advancedbilling.controllers.CustomersController;
import com.maxio.advancedbilling.exceptions.ApiException;
import com.maxio.advancedbilling.models.BasicDateField;
import com.maxio.advancedbilling.models.CreateCustomer;
diff --git a/tests/src/test/java/com/maxio/advancedbilling/controllers/CustomersControllerListSubscriptionsTest.java b/tests/src/test/java/com/maxio/advancedbilling/controllers/customers/CustomersControllerListSubscriptionsTest.java
similarity index 94%
rename from tests/src/test/java/com/maxio/advancedbilling/controllers/CustomersControllerListSubscriptionsTest.java
rename to tests/src/test/java/com/maxio/advancedbilling/controllers/customers/CustomersControllerListSubscriptionsTest.java
index 234dca96..3f55dcf0 100644
--- a/tests/src/test/java/com/maxio/advancedbilling/controllers/CustomersControllerListSubscriptionsTest.java
+++ b/tests/src/test/java/com/maxio/advancedbilling/controllers/customers/CustomersControllerListSubscriptionsTest.java
@@ -1,7 +1,12 @@
-package com.maxio.advancedbilling.controllers;
+package com.maxio.advancedbilling.controllers.customers;
import com.maxio.advancedbilling.AdvancedBillingClient;
import com.maxio.advancedbilling.TestClient;
+import com.maxio.advancedbilling.controllers.CustomersController;
+import com.maxio.advancedbilling.controllers.PaymentProfilesController;
+import com.maxio.advancedbilling.controllers.ProductFamiliesController;
+import com.maxio.advancedbilling.controllers.ProductsController;
+import com.maxio.advancedbilling.controllers.SubscriptionsController;
import com.maxio.advancedbilling.exceptions.ApiException;
import com.maxio.advancedbilling.models.CreateCustomer;
import com.maxio.advancedbilling.models.CreateCustomerRequest;
diff --git a/tests/src/test/java/com/maxio/advancedbilling/controllers/CustomersControllerReadTest.java b/tests/src/test/java/com/maxio/advancedbilling/controllers/customers/CustomersControllerReadTest.java
similarity index 97%
rename from tests/src/test/java/com/maxio/advancedbilling/controllers/CustomersControllerReadTest.java
rename to tests/src/test/java/com/maxio/advancedbilling/controllers/customers/CustomersControllerReadTest.java
index 2deadf6a..9ab5a62e 100644
--- a/tests/src/test/java/com/maxio/advancedbilling/controllers/CustomersControllerReadTest.java
+++ b/tests/src/test/java/com/maxio/advancedbilling/controllers/customers/CustomersControllerReadTest.java
@@ -1,6 +1,7 @@
-package com.maxio.advancedbilling.controllers;
+package com.maxio.advancedbilling.controllers.customers;
import com.maxio.advancedbilling.TestClient;
+import com.maxio.advancedbilling.controllers.CustomersController;
import com.maxio.advancedbilling.exceptions.ApiException;
import com.maxio.advancedbilling.models.CreateCustomer;
import com.maxio.advancedbilling.models.CreateCustomerRequest;
diff --git a/tests/src/test/java/com/maxio/advancedbilling/controllers/CustomersControllerUpdateTest.java b/tests/src/test/java/com/maxio/advancedbilling/controllers/customers/CustomersControllerUpdateTest.java
similarity index 98%
rename from tests/src/test/java/com/maxio/advancedbilling/controllers/CustomersControllerUpdateTest.java
rename to tests/src/test/java/com/maxio/advancedbilling/controllers/customers/CustomersControllerUpdateTest.java
index 41fba753..ab54296a 100644
--- a/tests/src/test/java/com/maxio/advancedbilling/controllers/CustomersControllerUpdateTest.java
+++ b/tests/src/test/java/com/maxio/advancedbilling/controllers/customers/CustomersControllerUpdateTest.java
@@ -1,6 +1,7 @@
-package com.maxio.advancedbilling.controllers;
+package com.maxio.advancedbilling.controllers.customers;
import com.maxio.advancedbilling.TestClient;
+import com.maxio.advancedbilling.controllers.CustomersController;
import com.maxio.advancedbilling.exceptions.ApiException;
import com.maxio.advancedbilling.exceptions.CustomerErrorResponseException;
import com.maxio.advancedbilling.models.CreateCustomer;
diff --git a/tests/src/test/java/com/maxio/advancedbilling/controllers/ProductPricePointsControllerListTest.java b/tests/src/test/java/com/maxio/advancedbilling/controllers/productpricepoints/ProductPricePointsControllerListTest.java
similarity index 97%
rename from tests/src/test/java/com/maxio/advancedbilling/controllers/ProductPricePointsControllerListTest.java
rename to tests/src/test/java/com/maxio/advancedbilling/controllers/productpricepoints/ProductPricePointsControllerListTest.java
index fa7ba271..cfa63a40 100644
--- a/tests/src/test/java/com/maxio/advancedbilling/controllers/ProductPricePointsControllerListTest.java
+++ b/tests/src/test/java/com/maxio/advancedbilling/controllers/productpricepoints/ProductPricePointsControllerListTest.java
@@ -1,7 +1,10 @@
-package com.maxio.advancedbilling.controllers;
+package com.maxio.advancedbilling.controllers.productpricepoints;
import com.maxio.advancedbilling.AdvancedBillingClient;
import com.maxio.advancedbilling.TestClient;
+import com.maxio.advancedbilling.controllers.ProductFamiliesController;
+import com.maxio.advancedbilling.controllers.ProductPricePointsController;
+import com.maxio.advancedbilling.controllers.ProductsController;
import com.maxio.advancedbilling.exceptions.ApiException;
import com.maxio.advancedbilling.models.CreateOrUpdateProduct;
import com.maxio.advancedbilling.models.CreateOrUpdateProductRequest;
diff --git a/tests/src/test/java/com/maxio/advancedbilling/controllers/products/ProductsControllerArchiveProductTest.java b/tests/src/test/java/com/maxio/advancedbilling/controllers/products/ProductsControllerArchiveProductTest.java
new file mode 100644
index 00000000..11e2ebea
--- /dev/null
+++ b/tests/src/test/java/com/maxio/advancedbilling/controllers/products/ProductsControllerArchiveProductTest.java
@@ -0,0 +1,62 @@
+package com.maxio.advancedbilling.controllers.products;
+
+import com.maxio.advancedbilling.exceptions.ApiException;
+import com.maxio.advancedbilling.exceptions.ErrorListResponseException;
+import com.maxio.advancedbilling.models.Product;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.time.Instant;
+
+import static com.maxio.advancedbilling.utils.CommonAssertions.assertNotFound;
+import static com.maxio.advancedbilling.utils.TimeUtils.parseStringTimestamp;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+public class ProductsControllerArchiveProductTest extends ProductsControllerTestBase {
+
+ @Test
+ void shouldArchiveProduct() throws IOException, ApiException {
+ // given
+ Product product = createProduct();
+
+ // when
+ String timestamp = Instant.now().toString();
+ productsController.archiveProduct(product.getId());
+
+ // then
+ Product archivedProduct = productsController.readProduct(product.getId()).getProduct();
+ assertAll(
+ () -> assertThat(archivedProduct.getId()).isEqualTo(product.getId()),
+ () -> assertThat(archivedProduct.getArchivedAt()).isNotNull(),
+ () -> assertThat(parseStringTimestamp(archivedProduct.getArchivedAt())).isAfterOrEqualTo(timestamp)
+ );
+ }
+
+ @Test
+ void shouldNotArchiveSameProductTwice() throws IOException, ApiException {
+ // given
+ Product product = createProduct();
+
+ // when
+ productsController.archiveProduct(product.getId());
+
+ // then
+ assertThatExceptionOfType(ErrorListResponseException.class)
+ .isThrownBy(() -> productsController.archiveProduct(product.getId())
+ )
+ .withMessage("Unprocessable Entity (WebDAV)")
+ .satisfies(e -> {
+ assertThat(e.getResponseCode()).isEqualTo(422);
+ assertThat(e.getErrors()).containsExactlyInAnyOrder("Product cannot be archived.");
+ });
+ }
+
+ @Test
+ void shouldNotArchiveNotOwnedProduct() {
+ // when-then
+ assertNotFound(() -> productsController.archiveProduct(99999999));
+ }
+
+}
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
new file mode 100644
index 00000000..821d5a0b
--- /dev/null
+++ b/tests/src/test/java/com/maxio/advancedbilling/controllers/products/ProductsControllerCreateProductTest.java
@@ -0,0 +1,277 @@
+package com.maxio.advancedbilling.controllers.products;
+
+import com.maxio.advancedbilling.exceptions.ApiException;
+import com.maxio.advancedbilling.exceptions.ErrorListResponseException;
+import com.maxio.advancedbilling.models.CreateOrUpdateProduct;
+import com.maxio.advancedbilling.models.CreateOrUpdateProductRequest;
+import com.maxio.advancedbilling.models.IntervalUnit;
+import com.maxio.advancedbilling.models.Product;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Stream;
+
+import static com.maxio.advancedbilling.utils.TimeUtils.parseStringTimestamp;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+public class ProductsControllerCreateProductTest extends ProductsControllerTestBase {
+
+ @Test
+ void shouldCreateProductWhenOnlyRequiredParametersAreProvided() throws IOException, ApiException {
+ // when
+ Instant timestamp = Instant.now().minus(5, ChronoUnit.SECONDS);
+ String handle = "washington-" + RandomStringUtils.randomAlphanumeric(5).toLowerCase();
+ Product product = productsController
+ .createProduct(productFamily.getId(), new CreateOrUpdateProductRequest(
+ new CreateOrUpdateProduct.Builder()
+ .name("Sample product")
+ .handle(handle)
+ .description("A sample product for testing")
+ .priceInCents(1000)
+ .interval(1)
+ .intervalUnit("month")
+ .requireCreditCard(true)
+ .build()
+ ))
+ .getProduct();
+
+ // then
+ assertAll(
+ () -> assertThat(product.getName()).isEqualTo("Sample product"),
+ () -> assertThat(product.getHandle()).isEqualTo(handle),
+ () -> assertThat(product.getDescription()).isEqualTo("A sample product for testing"),
+ () -> assertThat(product.getPriceInCents()).isEqualTo(1000),
+ () -> assertThat(product.getInterval()).isEqualTo(1),
+ () -> assertThat(product.getIntervalUnit().match(i -> i).value()).isEqualTo(IntervalUnit.MONTH.value()),
+
+ () -> assertThat(product.getId()).isNotNull(),
+ () -> assertThat(parseStringTimestamp(product.getCreatedAt())).isAfterOrEqualTo(timestamp),
+ () -> assertThat(parseStringTimestamp(product.getUpdatedAt())).isAfterOrEqualTo(timestamp),
+
+ () -> assertThat(product.getAccountingCode()).isNull(),
+ () -> assertThat(product.getRequestCreditCard()).isTrue(),
+ () -> assertThat(product.getExpirationInterval()).isNull(),
+ () -> assertThat(product.getExpirationIntervalUnit()).isNull(),
+ () -> assertThat(product.getInitialChargeInCents()).isNull(),
+ () -> assertThat(product.getTrialPriceInCents()).isNull(),
+ () -> assertThat(product.getTrialInterval()).isNull(),
+ () -> assertThat(product.getTrialIntervalUnit()).isNull(),
+ () -> assertThat(product.getArchivedAt()).isNull(),
+ () -> assertThat(product.getRequireCreditCard()).isTrue(),
+ () -> assertThat(product.getReturnParams()).isNull(),
+ () -> assertThat(product.getTaxable()).isFalse(),
+ () -> assertThat(product.getUpdateReturnUrl()).isNull(),
+ () -> assertThat(product.getInitialChargeAfterTrial()).isFalse(),
+ () -> assertThat(product.getVersionNumber()).isEqualTo(1),
+ () -> assertThat(product.getUpdateReturnParams()).isNull(),
+ () -> assertThat(product.getProductPricePointName()).isEqualTo("Original"),
+ () -> assertThat(product.getRequestBillingAddress()).isFalse(),
+ () -> assertThat(product.getRequireBillingAddress()).isFalse(),
+ () -> assertThat(product.getRequireShippingAddress()).isFalse(),
+ () -> assertThat(product.getTaxCode()).isNull(),
+ () -> assertThat(product.getDefaultProductPricePointId()).isPositive(),
+ () -> assertThat(product.getUseSiteExchangeRate()).isTrue(),
+ () -> assertThat(product.getItemCategory()).isNull(),
+ () -> assertThat(product.getProductPricePointId()).isPositive(),
+ () -> assertThat(product.getProductPricePointHandle()).isNotEmpty(),
+
+ () -> assertThat(product.getProductFamily()).isNotNull(),
+ () -> assertThat(product.getProductFamily().getId()).isEqualTo(productFamily.getId()),
+ () -> assertThat(product.getProductFamily().getName()).isEqualTo(productFamily.getName()),
+ () -> assertThat(product.getProductFamily().getHandle()).isEqualTo(productFamily.getHandle()),
+ () -> assertThat(product.getProductFamily().getAccountingCode()).isEqualTo(productFamily.getAccountingCode()),
+ () -> assertThat(product.getProductFamily().getDescription()).isEqualTo(productFamily.getDescription()),
+ () -> assertThat(product.getProductFamily().getCreatedAt()).isEqualTo(productFamily.getCreatedAt()),
+ () -> assertThat(product.getProductFamily().getUpdatedAt()).isEqualTo(productFamily.getUpdatedAt()),
+
+ () -> assertThat(product.getPublicSignupPages()).isEmpty()
+ );
+ }
+
+ @Test
+ void shouldCreateProductWhenAllParametersAreProvided() throws IOException, ApiException {
+ // when
+ Instant timestamp = Instant.now().minus(5, ChronoUnit.SECONDS);
+ String handle = "washington-" + RandomStringUtils.randomAlphanumeric(5).toLowerCase();
+ Product product = productsController
+ .createProduct(productFamily.getId(), new CreateOrUpdateProductRequest(
+ new CreateOrUpdateProduct.Builder()
+ .name("Sample product full")
+ .handle(handle)
+ .description("A sample product for testing")
+ .priceInCents(1000)
+ .interval(1)
+ .intervalUnit("month")
+ .requireCreditCard(true)
+
+ .accountingCode("code")
+ .autoCreateSignupPage(true)
+ .taxCode("taxcode")
+ .build()
+ ))
+ .getProduct();
+ // then
+ assertAll(
+ () -> assertThat(product.getName()).isEqualTo("Sample product full"),
+ () -> assertThat(product.getHandle()).isEqualTo(handle),
+ () -> assertThat(product.getDescription()).isEqualTo("A sample product for testing"),
+ () -> assertThat(product.getPriceInCents()).isEqualTo(1000),
+ () -> assertThat(product.getInterval()).isEqualTo(1),
+ () -> assertThat(product.getIntervalUnit().match(i -> i).value()).isEqualTo(IntervalUnit.MONTH.value()),
+ () -> assertThat(product.getTaxCode()).isEqualTo("taxcode"),
+ () -> assertThat(product.getAccountingCode()).isEqualTo("code"),
+
+ () -> assertThat(product.getId()).isNotNull(),
+ () -> assertThat(parseStringTimestamp(product.getCreatedAt())).isAfterOrEqualTo(timestamp),
+ () -> assertThat(parseStringTimestamp(product.getUpdatedAt())).isAfterOrEqualTo(timestamp),
+
+ () -> assertThat(product.getRequestCreditCard()).isTrue(),
+ () -> assertThat(product.getExpirationInterval()).isNull(),
+ () -> assertThat(product.getExpirationIntervalUnit()).isNull(),
+ () -> assertThat(product.getInitialChargeInCents()).isNull(),
+ () -> assertThat(product.getTrialPriceInCents()).isNull(),
+ () -> assertThat(product.getTrialInterval()).isNull(),
+ () -> assertThat(product.getTrialIntervalUnit()).isNull(),
+ () -> assertThat(product.getArchivedAt()).isNull(),
+ () -> assertThat(product.getRequireCreditCard()).isTrue(),
+ () -> assertThat(product.getReturnParams()).isNull(),
+ () -> assertThat(product.getTaxable()).isFalse(),
+ () -> assertThat(product.getUpdateReturnUrl()).isNull(),
+ () -> assertThat(product.getInitialChargeAfterTrial()).isFalse(),
+ () -> assertThat(product.getVersionNumber()).isEqualTo(1),
+ () -> assertThat(product.getUpdateReturnParams()).isNull(),
+ () -> assertThat(product.getProductPricePointName()).isEqualTo("Original"),
+ () -> assertThat(product.getRequestBillingAddress()).isFalse(),
+ () -> assertThat(product.getRequireBillingAddress()).isFalse(),
+ () -> assertThat(product.getRequireShippingAddress()).isFalse(),
+ () -> assertThat(product.getDefaultProductPricePointId()).isPositive(),
+ () -> assertThat(product.getUseSiteExchangeRate()).isTrue(),
+ () -> assertThat(product.getItemCategory()).isNull(),
+ () -> assertThat(product.getProductPricePointId()).isPositive(),
+ () -> assertThat(product.getProductPricePointHandle()).isNotEmpty(),
+
+ () -> assertThat(product.getProductFamily()).isNotNull(),
+ () -> assertThat(product.getProductFamily().getId()).isEqualTo(productFamily.getId()),
+ () -> assertThat(product.getProductFamily().getName()).isEqualTo(productFamily.getName()),
+ () -> assertThat(product.getProductFamily().getHandle()).isEqualTo(productFamily.getHandle()),
+ () -> assertThat(product.getProductFamily().getAccountingCode()).isEqualTo(productFamily.getAccountingCode()),
+ () -> assertThat(product.getProductFamily().getDescription()).isEqualTo(productFamily.getDescription()),
+ () -> assertThat(product.getProductFamily().getCreatedAt()).isEqualTo(productFamily.getCreatedAt()),
+ () -> assertThat(product.getProductFamily().getUpdatedAt()).isEqualTo(productFamily.getUpdatedAt()),
+
+ () -> assertThat(product.getPublicSignupPages().size()).isEqualTo(1),
+ () -> assertThat(product.getPublicSignupPages().get(0).getId()).isPositive(),
+ () -> assertThat(product.getPublicSignupPages().get(0).getReturnUrl()).isNull(),
+ () -> assertThat(product.getPublicSignupPages().get(0).getReturnParams()).isNull(),
+ () -> assertThat(product.getPublicSignupPages().get(0).getUrl()).isNotEmpty()
+ );
+ }
+
+ @Test
+ void shouldNotCreateProductWithExistingHandle() throws IOException, ApiException {
+ // when
+ String handle = "washington-" + RandomStringUtils.randomAlphanumeric(5).toLowerCase();
+ CreateOrUpdateProduct createOrUpdateProduct = new CreateOrUpdateProduct.Builder()
+ .name("Sample product full")
+ .handle(handle)
+ .priceInCents(1000)
+ .interval(1)
+ .intervalUnit("month")
+ .build();
+ Product product = productsController
+ .createProduct(productFamily.getId(), new CreateOrUpdateProductRequest(
+ createOrUpdateProduct
+ ))
+ .getProduct();
+ // then
+ String expectedErrorMessage = String.format(
+ "API Handle: must be unique - '%s' has been taken by another Product in this Site.",
+ handle);
+ assertThatExceptionOfType(ErrorListResponseException.class)
+ .isThrownBy(() -> productsController.createProduct(
+ productFamily.getId(), new CreateOrUpdateProductRequest(createOrUpdateProduct))
+ )
+ .withMessage("Unprocessable Entity (WebDAV)")
+ .satisfies(e -> {
+ assertThat(e.getResponseCode()).isEqualTo(422);
+ assertThat(e.getErrors()).containsExactlyInAnyOrder(expectedErrorMessage);
+ });
+ }
+
+ @ParameterizedTest
+ @MethodSource("argsForShouldNotCreateProductWhenBasicParametersAreBlank")
+ void shouldNotCreateProductWhenBasicParametersAreBlank(CreateOrUpdateProduct createProduct, List errorMessages) {
+ // when - then
+ assertThatExceptionOfType(ErrorListResponseException.class)
+ .isThrownBy(() -> productsController.createProduct(
+ productFamily.getId(), new CreateOrUpdateProductRequest(createProduct))
+ )
+ .withMessage("Unprocessable Entity (WebDAV)")
+ .satisfies(e -> {
+ assertThat(e.getResponseCode()).isEqualTo(422);
+ assertThat(e.getErrors()).hasSameElementsAs(errorMessages);
+ });
+ }
+
+ @Test
+ void shouldNotCreateProductForNotOwnedProductFamily() {
+ // when
+ String handle = "washington-" + RandomStringUtils.randomAlphanumeric(5).toLowerCase();
+ CreateOrUpdateProduct createOrUpdateProduct = new CreateOrUpdateProduct.Builder()
+ .name("Sample product full")
+ .handle(handle)
+ .priceInCents(1000)
+ .interval(1)
+ .intervalUnit("month")
+ .build();
+
+ // then
+ assertThatExceptionOfType(ApiException.class)
+ .isThrownBy(() -> productsController
+ .createProduct(999999, new CreateOrUpdateProductRequest(
+ createOrUpdateProduct
+ ))
+ )
+ .withMessage("HTTP Response Not OK")
+ .satisfies(e -> {
+ assertThat(e.getResponseCode()).isEqualTo(403);
+ assertThat(e.getHttpContext().getResponse().getBody()).isEqualTo("A valid product family id is required");
+ });
+ }
+
+ private static Stream argsForShouldNotCreateProductWhenBasicParametersAreBlank() {
+ CreateOrUpdateProduct productTemplate = new CreateOrUpdateProduct.Builder().name("test-name").handle("product-handle-test")
+ .description("test description").priceInCents(11).interval(1).intervalUnit("month").build();
+ return Stream.of(
+ Arguments.of(
+ productTemplate.toBuilder().name(null).build(), Collections.singletonList("Name: cannot be blank.")
+ ),
+ Arguments.of(
+ productTemplate.toBuilder().intervalUnit(null).build(), List.of("Interval unit: cannot be blank.",
+ "Interval unit: must be 'month' or 'day'.")
+ ),
+ Arguments.of(
+ productTemplate.toBuilder().handle("VERY INVALID HANDLE").build(),
+ List.of("API Handle: may only contain lowercase letters, numbers, underscores, and dashes")
+ ),
+ Arguments.of(
+ new CreateOrUpdateProduct(),
+ List.of("Name: cannot be blank.",
+ "Recurring Interval: must be greater than or equal to 1.",
+ "Interval unit: cannot be blank.",
+ "Interval unit: must be 'month' or 'day'.")
+ )
+ );
+ }
+
+}
diff --git a/tests/src/test/java/com/maxio/advancedbilling/controllers/products/ProductsControllerListProductsTest.java b/tests/src/test/java/com/maxio/advancedbilling/controllers/products/ProductsControllerListProductsTest.java
new file mode 100644
index 00000000..21cd210a
--- /dev/null
+++ b/tests/src/test/java/com/maxio/advancedbilling/controllers/products/ProductsControllerListProductsTest.java
@@ -0,0 +1,254 @@
+package com.maxio.advancedbilling.controllers.products;
+
+import com.maxio.advancedbilling.TestClient;
+import com.maxio.advancedbilling.controllers.ProductPricePointsController;
+import com.maxio.advancedbilling.exceptions.ApiException;
+import com.maxio.advancedbilling.models.CreateProductPricePoint;
+import com.maxio.advancedbilling.models.CreateProductPricePointRequest;
+import com.maxio.advancedbilling.models.ListProductsInput;
+import com.maxio.advancedbilling.models.Product;
+import com.maxio.advancedbilling.models.ProductPricePoint;
+import com.maxio.advancedbilling.models.ProductResponse;
+import org.apache.commons.lang3.time.DateUtils;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import static com.maxio.advancedbilling.models.BasicDateField.CREATED_AT;
+import static com.maxio.advancedbilling.utils.TimeUtils.parseStringTimestamp;
+import static com.maxio.advancedbilling.utils.TimeUtils.toTimestamp;
+import static com.maxio.advancedbilling.utils.TimeUtils.toTruncatedTimestamp;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ProductsControllerListProductsTest extends ProductsControllerTestBase {
+
+ static List savedProducts = new ArrayList<>();
+
+ @BeforeAll
+ static void setupProducts() throws IOException, ApiException {
+ archiveAllSiteProducts();
+
+ for (int i=0; i < 4; i++) {
+ savedProducts.add(createProductWithHandle("list-products-" + i));
+ }
+
+ // setup new price point for one product in order to test filtering by use_site_exchange_rate
+ Product productWithChangedPricePoint = createProductWithHandle("list-products-5");
+ ProductPricePointsController productPricePointsController = TestClient.createClient().getProductPricePointsController();
+ CreateProductPricePointRequest createProductPricePointRequest = new CreateProductPricePointRequest(
+ new CreateProductPricePoint.Builder().useSiteExchangeRate(false).interval(1).intervalUnit("month")
+ .priceInCents(22).name("Price point to promote").build()
+ );
+ ProductPricePoint pricePoint = productPricePointsController
+ .createProductPricePoint(productWithChangedPricePoint.getId(), createProductPricePointRequest)
+ .getPricePoint();
+ productPricePointsController.setDefaultPricePointForProduct(productWithChangedPricePoint.getId(), pricePoint.getId());
+ productWithChangedPricePoint = productsController.readProduct(productWithChangedPricePoint.getId()).getProduct();
+ savedProducts.add(productWithChangedPricePoint);
+ }
+
+ @Test
+ void shouldListProducts() throws IOException, ApiException {
+ // when
+ List productList = productsController.listProducts(new ListProductsInput())
+ .stream().map(ProductResponse::getProduct).toList();
+
+ // then
+ assertThat(productList).usingRecursiveFieldByFieldElementComparatorIgnoringFields("updatedAt")
+ .isEqualTo(savedProducts);
+ }
+
+ @Test
+ void shouldListProductsFilteringByStartDate() throws IOException, ApiException, ParseException {
+ // given
+ Date savedProductCreatedAt = parseStringTimestamp(savedProducts.get(0).getCreatedAt());
+ String startDateFilterIncludeElements = toTruncatedTimestamp(savedProductCreatedAt);
+ String startDateFilterExcludeElements = toTruncatedTimestamp(DateUtils
+ .addDays(savedProductCreatedAt, 1));
+
+ // when
+ List productList1 = productsController.listProducts(
+ new ListProductsInput.Builder().dateField(CREATED_AT).startDate(startDateFilterIncludeElements).build()
+ );
+ List productList2 = productsController.listProducts(
+ new ListProductsInput.Builder().dateField(CREATED_AT).startDate(startDateFilterExcludeElements).build()
+ );
+
+ // then
+ assertThat(productList1.stream().map(ProductResponse::getProduct).toList())
+ .usingRecursiveFieldByFieldElementComparatorIgnoringFields("updatedAt").isEqualTo(savedProducts);
+ assertThat(productList2).isEmpty();
+ }
+
+ @Test
+ void shouldListProductsFilteringByEndDate() throws IOException, ApiException, ParseException {
+ // given
+ Date savedProductCreatedAt = parseStringTimestamp(savedProducts.get(0).getCreatedAt());
+ String endDateFilterIncludeElements = toTruncatedTimestamp(savedProductCreatedAt);
+ String endDateFilterExcludeElements = toTruncatedTimestamp(DateUtils
+ .addDays(savedProductCreatedAt, -1));
+
+ // when
+ List productList1 = productsController.listProducts(
+ new ListProductsInput.Builder().dateField(CREATED_AT).endDate(endDateFilterIncludeElements).build()
+ );
+ List productList2 = productsController.listProducts(
+ new ListProductsInput.Builder().dateField(CREATED_AT).endDate(endDateFilterExcludeElements).build()
+ );
+
+ // then
+ assertThat(productList1.stream().map(ProductResponse::getProduct).toList())
+ .usingRecursiveFieldByFieldElementComparatorIgnoringFields("updatedAt").isEqualTo(savedProducts);
+ assertThat(productList2).isEmpty();
+
+ }
+
+ @Test
+ void shouldListProductsFilteringByStartDateTime() throws IOException, ApiException, ParseException {
+ // given
+ String savedProductCreatedAt = savedProducts.get(0).getCreatedAt();
+ String startDateTimeFilterExcludeElements = toTimestamp(DateUtils
+ .addMinutes(parseStringTimestamp(savedProductCreatedAt), 5));
+
+ // when
+ List productList1 = productsController.listProducts(
+ new ListProductsInput.Builder().dateField(CREATED_AT).startDatetime(savedProductCreatedAt).build()
+ );
+ List productList2 = productsController.listProducts(
+ new ListProductsInput.Builder().dateField(CREATED_AT).startDatetime(startDateTimeFilterExcludeElements).build()
+ );
+
+ // then
+ assertThat(productList1.stream().map(ProductResponse::getProduct).toList())
+ .usingRecursiveFieldByFieldElementComparatorIgnoringFields("updatedAt").isEqualTo(savedProducts);
+ assertThat(productList2).isEmpty();
+ }
+
+ @Test
+ void shouldListProductsFilteringByEndDateTime() throws IOException, ApiException, ParseException {
+ // given
+ Date savedProductCreatedAt = parseStringTimestamp(savedProducts.get(0).getCreatedAt());
+ String endDateTimeFilterIncludeElements = toTimestamp(DateUtils.addMinutes(savedProductCreatedAt, 5));
+ String endDateTimeFilterExcludeElements = toTimestamp(DateUtils.addMinutes(savedProductCreatedAt, -5));
+
+ // when
+ List productList1 = productsController.listProducts(
+ new ListProductsInput.Builder().dateField(CREATED_AT).endDatetime(endDateTimeFilterIncludeElements).build()
+ );
+ List productList2 = productsController.listProducts(
+ new ListProductsInput.Builder().dateField(CREATED_AT).endDatetime(endDateTimeFilterExcludeElements).build()
+ );
+
+ // then
+ assertThat(productList1.stream().map(ProductResponse::getProduct).toList())
+ .usingRecursiveFieldByFieldElementComparatorIgnoringFields("updatedAt").isEqualTo(savedProducts);
+ assertThat(productList2).isEmpty();
+ }
+
+ @Test
+ void shouldListProductsIncludingArchived() throws IOException, ApiException, InterruptedException {
+ // given
+ Thread.sleep(1000);
+ Product archivedProduct = createProductWithHandle("product-to-archive");
+ Product productAfterArchive = productsController.archiveProduct(archivedProduct.getId()).getProduct();
+ // when
+ // List products endpoint lists all products for site, so there is possibility that
+ // it will list some products created in other tests - that's why we're using additional
+ // date filter to narrow down to archived product created inside this test
+ List productListWithArchived = productsController.listProducts(
+ new ListProductsInput.Builder().dateField(CREATED_AT)
+ .startDatetime(archivedProduct.getCreatedAt()).includeArchived(true).build()
+ );
+ List productListWithoutArchived = productsController.listProducts(
+ new ListProductsInput.Builder().dateField(CREATED_AT)
+ .startDatetime(archivedProduct.getCreatedAt()).includeArchived(false).build()
+ );
+
+ // then
+ assertThat(productListWithArchived).hasSize(1);
+ assertThat(productListWithArchived.get(0).getProduct()).usingRecursiveComparison()
+ .isEqualTo(productAfterArchive);
+
+ assertThat(productListWithoutArchived).isEmpty();
+ }
+
+ @Test
+ void shouldListProductsFilteringByUseSiteExchangeRate() throws IOException, ApiException {
+ // given
+ Product productWithChangedPricePoint = savedProducts.get(4);
+
+ // when
+ List productsWithUseSiteExchangeRateFalse = productsController.listProducts(
+ new ListProductsInput.Builder().filterUseSiteExchangeRate(false).build()
+ );
+ List productsWithUseSiteExchangeRateTrue = productsController.listProducts(
+ new ListProductsInput.Builder().filterUseSiteExchangeRate(true).build()
+ );
+
+ // then
+ assertThat(productsWithUseSiteExchangeRateFalse).hasSize(1);
+ assertThat(productsWithUseSiteExchangeRateFalse.get(0).getProduct().getId())
+ .isEqualTo(productWithChangedPricePoint.getId());
+ assertThat(productsWithUseSiteExchangeRateFalse.get(0).getProduct().getUseSiteExchangeRate())
+ .isFalse();
+
+ assertThat(productsWithUseSiteExchangeRateTrue).hasSize(4);
+ assertThat(productsWithUseSiteExchangeRateTrue.stream().map(ProductResponse::getProduct).toList())
+ .usingRecursiveFieldByFieldElementComparatorIgnoringFields("updatedAt").containsExactlyInAnyOrderElementsOf(savedProducts.subList(0,4));
+ }
+
+ @Test
+ void shouldListProductsUsingPagination() throws IOException, ApiException {
+ // when
+ List listProductsPage1 = productsController
+ .listProducts(new ListProductsInput.Builder()
+ .page(1)
+ .perPage(2)
+ .build()
+ );
+ List listProductsPage2 = productsController
+ .listProducts(new ListProductsInput.Builder()
+ .page(2)
+ .perPage(2)
+ .build()
+ );
+ List listProductsPage3 = productsController
+ .listProducts(new ListProductsInput.Builder()
+ .page(3)
+ .perPage(2)
+ .build()
+ );
+ List listProductsPage4 = productsController
+ .listProducts(new ListProductsInput.Builder()
+ .page(4)
+ .perPage(2)
+ .build()
+ );
+
+ // then
+ assertThat(listProductsPage1).hasSize(2);
+ assertThat(listProductsPage2).hasSize(2);
+ assertThat(listProductsPage3).hasSize(1);
+ assertThat(listProductsPage4).isEmpty();
+ }
+
+ private static void archiveAllSiteProducts() throws IOException, ApiException {
+ List productResponses = productsController.listProducts(
+ new ListProductsInput.Builder().perPage(200).build()
+ );
+ while (productResponses.size() > 0) {
+ for (ProductResponse p: productResponses) {
+ productsController.archiveProduct(p.getProduct().getId());
+ }
+ productResponses = productsController.listProducts(
+ new ListProductsInput.Builder().perPage(200).build()
+ );
+ }
+ }
+
+}
diff --git a/tests/src/test/java/com/maxio/advancedbilling/controllers/products/ProductsControllerReadProductByHandleTest.java b/tests/src/test/java/com/maxio/advancedbilling/controllers/products/ProductsControllerReadProductByHandleTest.java
new file mode 100644
index 00000000..051e561a
--- /dev/null
+++ b/tests/src/test/java/com/maxio/advancedbilling/controllers/products/ProductsControllerReadProductByHandleTest.java
@@ -0,0 +1,33 @@
+package com.maxio.advancedbilling.controllers.products;
+
+import com.maxio.advancedbilling.exceptions.ApiException;
+import com.maxio.advancedbilling.models.IntervalUnit;
+import com.maxio.advancedbilling.models.Product;
+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;
+
+public class ProductsControllerReadProductByHandleTest extends ProductsControllerTestBase {
+
+ @Test
+ void shouldReadProduct() throws IOException, ApiException {
+ // given
+ Product product = createProduct();
+
+ // when
+ Product readProduct = productsController.readProductByHandle(product.getHandle()).getProduct();
+
+ // then
+ assertThat(readProduct).usingRecursiveComparison().isEqualTo(product);
+ }
+
+ @Test
+ void shouldNotReadNotOwnedProduct() {
+ // when-then
+ assertNotFound(() -> productsController.readProductByHandle("abc"));
+ }
+
+}
diff --git a/tests/src/test/java/com/maxio/advancedbilling/controllers/products/ProductsControllerReadProductTest.java b/tests/src/test/java/com/maxio/advancedbilling/controllers/products/ProductsControllerReadProductTest.java
new file mode 100644
index 00000000..7ab035f1
--- /dev/null
+++ b/tests/src/test/java/com/maxio/advancedbilling/controllers/products/ProductsControllerReadProductTest.java
@@ -0,0 +1,32 @@
+package com.maxio.advancedbilling.controllers.products;
+
+import com.maxio.advancedbilling.exceptions.ApiException;
+import com.maxio.advancedbilling.models.Product;
+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;
+
+public class ProductsControllerReadProductTest extends ProductsControllerTestBase {
+
+ @Test
+ void shouldReadProduct() throws IOException, ApiException {
+ // given
+ Product product = createProduct();
+
+ // when
+ Product readProduct = productsController.readProduct(product.getId()).getProduct();
+
+ // then
+ assertThat(readProduct).usingRecursiveComparison().isEqualTo(product);
+ }
+
+ @Test
+ void shouldNotArchiveNotOwnedProduct() {
+ // when-then
+ assertNotFound(() -> productsController.readProduct(99999999));
+ }
+
+}
diff --git a/tests/src/test/java/com/maxio/advancedbilling/controllers/products/ProductsControllerTestBase.java b/tests/src/test/java/com/maxio/advancedbilling/controllers/products/ProductsControllerTestBase.java
new file mode 100644
index 00000000..91325627
--- /dev/null
+++ b/tests/src/test/java/com/maxio/advancedbilling/controllers/products/ProductsControllerTestBase.java
@@ -0,0 +1,53 @@
+package com.maxio.advancedbilling.controllers.products;
+
+import com.maxio.advancedbilling.TestClient;
+import com.maxio.advancedbilling.controllers.ProductFamiliesController;
+import com.maxio.advancedbilling.controllers.ProductsController;
+import com.maxio.advancedbilling.exceptions.ApiException;
+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.Product;
+import com.maxio.advancedbilling.models.ProductFamily;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.jupiter.api.BeforeAll;
+
+import java.io.IOException;
+
+abstract class ProductsControllerTestBase {
+ protected static final ProductsController productsController = TestClient.createClient().getProductsController();
+ protected static ProductFamily productFamily;
+
+ @BeforeAll
+ static void setup() throws IOException, ApiException {
+ ProductFamiliesController productFamiliesController = TestClient.createClient()
+ .getProductFamiliesController();
+
+ productFamily = productFamiliesController.createProductFamily(new CreateProductFamilyRequest(
+ new CreateProductFamily("Test Product Family "
+ + RandomStringUtils.randomAlphanumeric(5), null)))
+ .getProductFamily();
+ }
+
+ protected Product createProduct() throws IOException, ApiException {
+ return createProductWithHandle("initial_handle-" + RandomStringUtils.randomAlphanumeric(5).toLowerCase());
+ }
+
+ protected static Product createProductWithHandle(String handle) throws IOException, ApiException {
+ Product product = productsController
+ .createProduct(productFamily.getId(), new CreateOrUpdateProductRequest(
+ new CreateOrUpdateProduct.Builder()
+ .name("Initial Sample product-" + RandomStringUtils.randomAlphanumeric(5))
+ .handle(handle)
+ .description("A sample product for testing")
+ .priceInCents(1000)
+ .interval(1)
+ .intervalUnit("month")
+ .build()
+ ))
+ .getProduct();
+ return product;
+ }
+
+}
diff --git a/tests/src/test/java/com/maxio/advancedbilling/controllers/products/ProductsControllerUpdateProductTest.java b/tests/src/test/java/com/maxio/advancedbilling/controllers/products/ProductsControllerUpdateProductTest.java
new file mode 100644
index 00000000..485880d9
--- /dev/null
+++ b/tests/src/test/java/com/maxio/advancedbilling/controllers/products/ProductsControllerUpdateProductTest.java
@@ -0,0 +1,171 @@
+package com.maxio.advancedbilling.controllers.products;
+
+import com.maxio.advancedbilling.exceptions.ApiException;
+import com.maxio.advancedbilling.exceptions.ErrorListResponseException;
+import com.maxio.advancedbilling.models.CreateOrUpdateProduct;
+import com.maxio.advancedbilling.models.CreateOrUpdateProductRequest;
+import com.maxio.advancedbilling.models.IntervalUnit;
+import com.maxio.advancedbilling.models.Product;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Stream;
+
+import static com.maxio.advancedbilling.utils.CommonAssertions.assertNotFound;
+import static com.maxio.advancedbilling.utils.TimeUtils.parseStringTimestamp;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+public class ProductsControllerUpdateProductTest extends ProductsControllerTestBase {
+
+ @Test
+ void shouldUpdateProductWithAllParameters() throws IOException, ApiException {
+ // when
+ Instant timestamp = Instant.now().minus(5, ChronoUnit.SECONDS);
+ Product product = createProduct();
+
+ Product updatedProduct = productsController.updateProduct(product.getId(), new CreateOrUpdateProductRequest(
+ new CreateOrUpdateProduct.Builder()
+ .name("Updated Product")
+ .handle("updated_handle")
+ .description("Updated Description")
+ .priceInCents(2000)
+ .interval(2)
+ .intervalUnit(IntervalUnit.DAY.value())
+
+ .accountingCode("acccode")
+ .taxCode("taxcode")
+ .requireCreditCard(true)
+ .build()
+ )).getProduct();
+ // then
+ assertAll(
+ () -> assertThat(updatedProduct.getId()).isEqualTo(product.getId()),
+ () -> assertThat(updatedProduct.getName()).isEqualTo("Updated Product"),
+ () -> assertThat(updatedProduct.getHandle()).isEqualTo("updated_handle"),
+ () -> assertThat(updatedProduct.getDescription()).isEqualTo("Updated Description"),
+ () -> assertThat(updatedProduct.getPriceInCents()).isEqualTo(2000),
+ () -> assertThat(updatedProduct.getInterval()).isEqualTo(2),
+ () -> assertThat(updatedProduct.getIntervalUnit().match(i -> i).value()).isEqualTo(IntervalUnit.DAY.value()),
+ () -> assertThat(parseStringTimestamp(updatedProduct.getCreatedAt())).isAfterOrEqualTo(timestamp),
+ () -> assertThat(parseStringTimestamp(updatedProduct.getUpdatedAt())).isAfterOrEqualTo(timestamp),
+ () -> assertThat(updatedProduct.getAccountingCode()).isEqualTo("acccode"),
+ () -> assertThat(updatedProduct.getRequestCreditCard()).isTrue(),
+ () -> assertThat(updatedProduct.getTaxCode()).isEqualTo("taxcode"),
+
+ () -> assertThat(updatedProduct.getExpirationInterval()).isNull(),
+ () -> assertThat(updatedProduct.getExpirationIntervalUnit()).isNull(),
+ () -> assertThat(updatedProduct.getInitialChargeInCents()).isNull(),
+ () -> assertThat(updatedProduct.getTrialPriceInCents()).isNull(),
+ () -> assertThat(updatedProduct.getTrialInterval()).isNull(),
+ () -> assertThat(updatedProduct.getTrialIntervalUnit()).isNull(),
+ () -> assertThat(updatedProduct.getArchivedAt()).isNull(),
+ () -> assertThat(updatedProduct.getRequireCreditCard()).isTrue(),
+ () -> assertThat(updatedProduct.getReturnParams()).isNull(),
+ () -> assertThat(updatedProduct.getTaxable()).isFalse(),
+ () -> assertThat(updatedProduct.getUpdateReturnUrl()).isNull(),
+ () -> assertThat(updatedProduct.getInitialChargeAfterTrial()).isFalse(),
+ () -> assertThat(updatedProduct.getVersionNumber()).isEqualTo(1),
+ () -> assertThat(updatedProduct.getUpdateReturnParams()).isNull(),
+ () -> assertThat(updatedProduct.getProductPricePointName()).isEqualTo("Default"),
+ () -> assertThat(updatedProduct.getRequestBillingAddress()).isFalse(),
+ () -> assertThat(updatedProduct.getRequireBillingAddress()).isFalse(),
+ () -> assertThat(updatedProduct.getRequireShippingAddress()).isFalse(),
+ () -> assertThat(updatedProduct.getDefaultProductPricePointId()).isPositive(),
+ () -> assertThat(updatedProduct.getUseSiteExchangeRate()).isTrue(),
+ () -> assertThat(updatedProduct.getItemCategory()).isNull(),
+ () -> assertThat(updatedProduct.getProductPricePointId()).isPositive(),
+ () -> assertThat(updatedProduct.getProductPricePointHandle()).isNotEmpty(),
+
+ () -> assertThat(updatedProduct.getProductFamily()).isNotNull(),
+ () -> assertThat(updatedProduct.getProductFamily().getId()).isEqualTo(productFamily.getId()),
+ () -> assertThat(updatedProduct.getProductFamily().getName()).isEqualTo(productFamily.getName()),
+ () -> assertThat(updatedProduct.getProductFamily().getHandle()).isEqualTo(productFamily.getHandle()),
+ () -> assertThat(updatedProduct.getProductFamily().getAccountingCode()).isEqualTo(productFamily.getAccountingCode()),
+ () -> assertThat(updatedProduct.getProductFamily().getDescription()).isEqualTo(productFamily.getDescription()),
+ () -> assertThat(updatedProduct.getProductFamily().getCreatedAt()).isEqualTo(productFamily.getCreatedAt()),
+ () -> assertThat(updatedProduct.getProductFamily().getUpdatedAt()).isEqualTo(productFamily.getUpdatedAt())
+ );
+ }
+
+ @Test
+ void shouldNotUpdateProductHandleToHandleOfExistingProduct() throws IOException, ApiException {
+ // when
+ Product product1 = createProduct();
+ Product product2 = createProduct();
+
+ // then
+ String expectedErrorMessage = String.format(
+ "API Handle: must be unique - '%s' has been taken by another Product in this Site.",
+ product1.getHandle());
+ assertThatExceptionOfType(ErrorListResponseException.class)
+ .isThrownBy(() -> productsController.updateProduct(
+ product2.getId(), new CreateOrUpdateProductRequest(
+ new CreateOrUpdateProduct.Builder()
+ .name("Updated Product")
+ .handle(product1.getHandle())
+ .description("Updated Description")
+ .priceInCents(2000)
+ .interval(2)
+ .intervalUnit(IntervalUnit.DAY.value())
+ .build()
+ ))
+ )
+ .withMessage("Unprocessable Entity (WebDAV)")
+ .satisfies(e -> {
+ assertThat(e.getResponseCode()).isEqualTo(422);
+ assertThat(e.getErrors()).containsExactlyInAnyOrder(expectedErrorMessage);
+
+ });
+ }
+
+ @ParameterizedTest
+ @MethodSource("argsForShouldNotUpdateProductWithBlankBasicParameters")
+ void shouldNotUpdateProductWithBlankBasicParameters(CreateOrUpdateProduct updateProduct,
+ List errorMessages) throws IOException, ApiException {
+ // when - then
+ Product product = createProduct();
+ assertThatExceptionOfType(ErrorListResponseException.class)
+ .isThrownBy(() -> productsController.updateProduct(
+ product.getId(), new CreateOrUpdateProductRequest(updateProduct))
+ )
+ .withMessage("Unprocessable Entity (WebDAV)")
+ .satisfies(e -> {
+ assertThat(e.getResponseCode()).isEqualTo(422);
+ assertThat(e.getErrors()).hasSameElementsAs(errorMessages);
+ });
+ }
+
+ @Test
+ void shouldNotUpdateNotOwnedProduct() {
+ // when-then
+ assertNotFound(() -> productsController.updateProduct(999999, new CreateOrUpdateProductRequest()));
+ }
+
+ private static Stream argsForShouldNotUpdateProductWithBlankBasicParameters() {
+ CreateOrUpdateProduct productTemplate = new CreateOrUpdateProduct.Builder().name("test-name").handle("product-handle-test")
+ .description("test description").priceInCents(11).interval(1).intervalUnit("month").build();
+ return Stream.of(
+ Arguments.of(
+ productTemplate.toBuilder().name(null).build(), Collections.singletonList("Name: cannot be blank.")
+ ),
+ Arguments.of(
+ productTemplate.toBuilder().intervalUnit(null).build(), List.of("Interval unit: cannot be blank.",
+ "Interval unit: must be 'month' or 'day'.")
+ ),
+ Arguments.of(
+ productTemplate.toBuilder().handle("VERY INVALID HANDLE").build(),
+ List.of("API Handle: may only contain lowercase letters, numbers, underscores, and dashes")
+ )
+ );
+ }
+
+}
diff --git a/tests/src/test/java/com/maxio/advancedbilling/controllers/SitesControllerTest.java b/tests/src/test/java/com/maxio/advancedbilling/controllers/sites/SitesControllerTest.java
similarity index 96%
rename from tests/src/test/java/com/maxio/advancedbilling/controllers/SitesControllerTest.java
rename to tests/src/test/java/com/maxio/advancedbilling/controllers/sites/SitesControllerTest.java
index 608a2453..affae00b 100644
--- a/tests/src/test/java/com/maxio/advancedbilling/controllers/SitesControllerTest.java
+++ b/tests/src/test/java/com/maxio/advancedbilling/controllers/sites/SitesControllerTest.java
@@ -1,6 +1,7 @@
-package com.maxio.advancedbilling.controllers;
+package com.maxio.advancedbilling.controllers.sites;
import com.maxio.advancedbilling.TestClient;
+import com.maxio.advancedbilling.controllers.SitesController;
import com.maxio.advancedbilling.models.AllocationSettings;
import com.maxio.advancedbilling.models.NetTerms;
import com.maxio.advancedbilling.models.OrganizationAddress;
diff --git a/tests/src/test/java/com/maxio/advancedbilling/utils/TimeUtils.java b/tests/src/test/java/com/maxio/advancedbilling/utils/TimeUtils.java
new file mode 100644
index 00000000..530bc0fa
--- /dev/null
+++ b/tests/src/test/java/com/maxio/advancedbilling/utils/TimeUtils.java
@@ -0,0 +1,26 @@
+package com.maxio.advancedbilling.utils;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class TimeUtils {
+
+ private static final SimpleDateFormat AB_DATE_FORMAT =
+ new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
+ private static final SimpleDateFormat TRUNCATED_DATE_FORMAT =
+ new SimpleDateFormat("yyyy-MM-dd");
+
+ public static Date parseStringTimestamp(String source) throws ParseException {
+ return AB_DATE_FORMAT.parse(source);
+ }
+
+ public static String toTimestamp(Date source) {
+ return AB_DATE_FORMAT.format(source);
+ }
+
+ public static String toTruncatedTimestamp(Date source) {
+ return TRUNCATED_DATE_FORMAT.format(source);
+ }
+
+}